Coverage for typer / core.py: 100%
329 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-09 12:36 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-09 12:36 +0000
1import errno 1bfgadech
2import importlib.util 1bfgadech
3import inspect 1bfgadech
4import os 1bfgadech
5import sys 1bfgadech
6from collections.abc import MutableMapping, Sequence 1bfgadech
7from difflib import get_close_matches 1bfgadech
8from enum import Enum 1bfgadech
9from gettext import gettext as _ 1bfgadech
10from typing import ( 1bfgadech
11 Any,
12 Callable,
13 Optional,
14 TextIO,
15 Union,
16 cast,
17)
19import click 1bfgadech
20import click.core 1bfgadech
21import click.formatting 1bfgadech
22import click.shell_completion 1bfgadech
23import click.types 1bfgadech
24import click.utils 1bfgadech
26from ._typing import Literal 1bfgadech
28MarkupMode = Literal["markdown", "rich", None] 1bfgadech
29MARKUP_MODE_KEY = "TYPER_RICH_MARKUP_MODE" 1bfgadech
31HAS_RICH = importlib.util.find_spec("rich") is not None 1bfgadech
32HAS_SHELLINGHAM = importlib.util.find_spec("shellingham") is not None 1bfgadech
34if HAS_RICH: 1bfgadech
35 DEFAULT_MARKUP_MODE: MarkupMode = "rich" 1bfgadech
36else: # pragma: no cover
37 DEFAULT_MARKUP_MODE = None
40# Copy from click.parser._split_opt
41def _split_opt(opt: str) -> tuple[str, str]: 1bfgadech
42 first = opt[:1] 1bfgadech
43 if first.isalnum(): 1bfgadech
44 return "", opt 1bfgadech
45 if opt[1:2] == first: 1bfgadech
46 return opt[:2], opt[2:] 1bfgadech
47 return first, opt[1:] 1bfgadech
50def _typer_param_setup_autocompletion_compat( 1bfgadech
51 self: click.Parameter,
52 *,
53 autocompletion: Optional[
54 Callable[[click.Context, list[str], str], list[Union[tuple[str, str], str]]]
55 ] = None,
56) -> None:
57 if self._custom_shell_complete is not None: 1bfgadech
58 import warnings 1bfgadech
60 warnings.warn( 1bfgadech
61 "In Typer, only the parameter 'autocompletion' is supported. "
62 "The support for 'shell_complete' is deprecated and will be removed in upcoming versions. ",
63 DeprecationWarning,
64 stacklevel=2,
65 )
67 if autocompletion is not None: 1bfgadech
69 def compat_autocompletion( 1bfgadech
70 ctx: click.Context, param: click.core.Parameter, incomplete: str
71 ) -> list["click.shell_completion.CompletionItem"]:
72 from click.shell_completion import CompletionItem 1bfgadech
74 out = [] 1bfgadech
76 for c in autocompletion(ctx, [], incomplete): 1bfgadech
77 if isinstance(c, tuple): 1bfgadech
78 use_completion = CompletionItem(c[0], help=c[1]) 1bfgadech
79 else:
80 assert isinstance(c, str) 1bfgadech
81 use_completion = CompletionItem(c) 1bfgadech
83 if use_completion.value.startswith(incomplete): 1bfgadech
84 out.append(use_completion) 1bfgadech
86 return out 1bfgadech
88 self._custom_shell_complete = compat_autocompletion 1bfgadech
91def _get_default_string( 1bfgadech
92 obj: Union["TyperArgument", "TyperOption"],
93 *,
94 ctx: click.Context,
95 show_default_is_str: bool,
96 default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any],
97) -> str:
98 # Extracted from click.core.Option.get_help_record() to be reused by
99 # rich_utils avoiding RegEx hacks
100 if show_default_is_str: 1bfgadech
101 default_string = f"({obj.show_default})" 1bfgadech
102 elif isinstance(default_value, (list, tuple)): 1bfgadech
103 default_string = ", ".join( 1bfgadech
104 _get_default_string(
105 obj, ctx=ctx, show_default_is_str=show_default_is_str, default_value=d
106 )
107 for d in default_value
108 )
109 elif isinstance(default_value, Enum): 1bfgadech
110 default_string = str(default_value.value) 1bfgadech
111 elif inspect.isfunction(default_value): 1bfgadech
112 default_string = _("(dynamic)") 1bfgadech
113 elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: 1bfgadech
114 # For boolean flags that have distinct True/False opts,
115 # use the opt without prefix instead of the value.
116 # Typer override, original commented
117 # default_string = click.parser.split_opt(
118 # (self.opts if self.default else self.secondary_opts)[0]
119 # )[1]
120 if obj.default: 1bfgadech
121 if obj.opts: 1bfgadech
122 default_string = _split_opt(obj.opts[0])[1] 1bfgadech
123 else:
124 default_string = str(default_value) 1bfgadech
125 else:
126 default_string = _split_opt(obj.secondary_opts[0])[1] 1bfgadech
127 # Typer override end
128 elif ( 1bac
129 isinstance(obj, TyperOption)
130 and obj.is_bool_flag
131 and not obj.secondary_opts
132 and not default_value
133 ):
134 default_string = "" 1bfgadech
135 else:
136 default_string = str(default_value) 1bfgadech
137 return default_string 1bfgadech
140def _extract_default_help_str( 1bfgadech
141 obj: Union["TyperArgument", "TyperOption"], *, ctx: click.Context
142) -> Optional[Union[Any, Callable[[], Any]]]:
143 # Extracted from click.core.Option.get_help_record() to be reused by
144 # rich_utils avoiding RegEx hacks
145 # Temporarily enable resilient parsing to avoid type casting
146 # failing for the default. Might be possible to extend this to
147 # help formatting in general.
148 resilient = ctx.resilient_parsing 1bfgadech
149 ctx.resilient_parsing = True 1bfgadech
151 try: 1bfgadech
152 default_value = obj.get_default(ctx, call=False) 1bfgadech
153 finally:
154 ctx.resilient_parsing = resilient 1bfgadech
155 return default_value 1bfgadech
158def _main( 1bfgadech
159 self: click.Command,
160 *,
161 args: Optional[Sequence[str]] = None,
162 prog_name: Optional[str] = None,
163 complete_var: Optional[str] = None,
164 standalone_mode: bool = True,
165 windows_expand_args: bool = True,
166 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
167 **extra: Any,
168) -> Any:
169 # Typer override, duplicated from click.main() to handle custom rich exceptions
170 # Verify that the environment is configured correctly, or reject
171 # further execution to avoid a broken script.
172 if args is None: 1bfgadech
173 args = sys.argv[1:] 1bfgadech
175 # Covered in Click tests
176 if os.name == "nt" and windows_expand_args: # pragma: no cover 1bfgadech
177 args = click.utils._expand_args(args) 1ade
178 else:
179 args = list(args) 1bfgadech
181 if prog_name is None: 1bfgadech
182 prog_name = click.utils._detect_program_name() 1bfgadech
184 # Process shell completion requests and exit early.
185 self._main_shell_completion(extra, prog_name, complete_var) 1bfgadech
187 try: 1bfgadech
188 try: 1bfgadech
189 with self.make_context(prog_name, args, **extra) as ctx: 1bfgadech
190 rv = self.invoke(ctx) 1bfgadech
191 if not standalone_mode: 1bfgadech
192 return rv 1bfgadech
193 # it's not safe to `ctx.exit(rv)` here!
194 # note that `rv` may actually contain data like "1" which
195 # has obvious effects
196 # more subtle case: `rv=[None, None]` can come out of
197 # chained commands which all returned `None` -- so it's not
198 # even always obvious that `rv` indicates success/failure
199 # by its truthiness/falsiness
200 ctx.exit() 1bfgadech
201 except EOFError as e: 1bfgadech
202 click.echo(file=sys.stderr) 1bfgadech
203 raise click.Abort() from e 1bfgadech
204 except KeyboardInterrupt as e: 1bfgadech
205 raise click.exceptions.Exit(130) from e 1bfgadech
206 except click.ClickException as e: 1bfgadech
207 if not standalone_mode: 1bfgadech
208 raise 1bfgadech
209 # Typer override
210 if HAS_RICH and rich_markup_mode is not None: 1bfgadech
211 from . import rich_utils 1bfgadech
213 rich_utils.rich_format_error(e) 1bfgadech
214 else:
215 e.show() 1bfgadech
216 # Typer override end
217 sys.exit(e.exit_code) 1bfgadech
218 except OSError as e: 1bfgadech
219 if e.errno == errno.EPIPE: 1bfgadech
220 sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout)) 1bfgadech
221 sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr)) 1bfgadech
222 sys.exit(1) 1bfgadech
223 else:
224 raise 1bfgadech
225 except click.exceptions.Exit as e: 1bfgadech
226 if standalone_mode: 1bfgadech
227 sys.exit(e.exit_code) 1bfgadech
228 else:
229 # in non-standalone mode, return the exit code
230 # note that this is only reached if `self.invoke` above raises
231 # an Exit explicitly -- thus bypassing the check there which
232 # would return its result
233 # the results of non-standalone execution may therefore be
234 # somewhat ambiguous: if there are codepaths which lead to
235 # `ctx.exit(1)` and to `return 1`, the caller won't be able to
236 # tell the difference between the two
237 return e.exit_code 1bfgadech
238 except click.Abort: 1bfgadech
239 if not standalone_mode: 1bfgadech
240 raise 1bfgadech
241 # Typer override
242 if HAS_RICH and rich_markup_mode is not None: 1bfgadech
243 from . import rich_utils 1bfgadech
245 rich_utils.rich_abort_error() 1bfgadech
246 else:
247 click.echo(_("Aborted!"), file=sys.stderr) 1bfgadech
248 # Typer override end
249 sys.exit(1) 1bfgadech
252class TyperArgument(click.core.Argument): 1bfgadech
253 def __init__( 1bfgadech
254 self,
255 *,
256 # Parameter
257 param_decls: list[str],
258 type: Optional[Any] = None,
259 required: Optional[bool] = None,
260 default: Optional[Any] = None,
261 callback: Optional[Callable[..., Any]] = None,
262 nargs: Optional[int] = None,
263 metavar: Optional[str] = None,
264 expose_value: bool = True,
265 is_eager: bool = False,
266 envvar: Optional[Union[str, list[str]]] = None,
267 # Note that shell_complete is not fully supported and will be removed in future versions
268 # TODO: Remove shell_complete in a future version (after 0.16.0)
269 shell_complete: Optional[
270 Callable[
271 [click.Context, click.Parameter, str],
272 Union[list["click.shell_completion.CompletionItem"], list[str]],
273 ]
274 ] = None,
275 autocompletion: Optional[Callable[..., Any]] = None,
276 # TyperArgument
277 show_default: Union[bool, str] = True,
278 show_choices: bool = True,
279 show_envvar: bool = True,
280 help: Optional[str] = None,
281 hidden: bool = False,
282 # Rich settings
283 rich_help_panel: Union[str, None] = None,
284 ):
285 self.help = help 1bfgadech
286 self.show_default = show_default 1bfgadech
287 self.show_choices = show_choices 1bfgadech
288 self.show_envvar = show_envvar 1bfgadech
289 self.hidden = hidden 1bfgadech
290 self.rich_help_panel = rich_help_panel 1bfgadech
292 super().__init__( 1bfgadech
293 param_decls=param_decls,
294 type=type,
295 required=required,
296 default=default,
297 callback=callback,
298 nargs=nargs,
299 metavar=metavar,
300 expose_value=expose_value,
301 is_eager=is_eager,
302 envvar=envvar,
303 shell_complete=shell_complete,
304 )
305 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1bfgadech
307 def _get_default_string( 1bfgadech
308 self,
309 *,
310 ctx: click.Context,
311 show_default_is_str: bool,
312 default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any],
313 ) -> str:
314 return _get_default_string( 1bfgadech
315 self,
316 ctx=ctx,
317 show_default_is_str=show_default_is_str,
318 default_value=default_value,
319 )
321 def _extract_default_help_str( 1bfgadech
322 self, *, ctx: click.Context
323 ) -> Optional[Union[Any, Callable[[], Any]]]:
324 return _extract_default_help_str(self, ctx=ctx) 1bfgadech
326 def get_help_record(self, ctx: click.Context) -> Optional[tuple[str, str]]: 1bfgadech
327 # Modified version of click.core.Option.get_help_record()
328 # to support Arguments
329 if self.hidden: 1bfgadech
330 return None 1bfgadech
331 name = self.make_metavar(ctx=ctx) 1bfgadech
332 help = self.help or "" 1bfgadech
333 extra = [] 1bfgadech
334 if self.show_envvar: 1bfgadech
335 envvar = self.envvar 1bfgadech
336 # allow_from_autoenv is currently not supported in Typer for CLI Arguments
337 if envvar is not None: 1bfgadech
338 var_str = ( 1bfgadech
339 ", ".join(str(d) for d in envvar)
340 if isinstance(envvar, (list, tuple))
341 else envvar
342 )
343 extra.append(f"env var: {var_str}") 1bfgadech
345 # Typer override:
346 # Extracted to _extract_default_help_str() to allow re-using it in rich_utils
347 default_value = self._extract_default_help_str(ctx=ctx) 1bfgadech
348 # Typer override end
350 show_default_is_str = isinstance(self.show_default, str) 1bfgadech
352 if show_default_is_str or ( 1bfgadech
353 default_value is not None and (self.show_default or ctx.show_default)
354 ):
355 # Typer override:
356 # Extracted to _get_default_string() to allow re-using it in rich_utils
357 default_string = self._get_default_string( 1bfgadech
358 ctx=ctx,
359 show_default_is_str=show_default_is_str,
360 default_value=default_value,
361 )
362 # Typer override end
363 if default_string: 1bfgadech
364 extra.append(_("default: {default}").format(default=default_string)) 1bfgadech
365 if self.required: 1bfgadech
366 extra.append(_("required")) 1bfgadech
367 if extra: 1bfgadech
368 extra_str = "; ".join(extra) 1bfgadech
369 extra_str = f"[{extra_str}]" 1bfgadech
370 rich_markup_mode = None 1bfgadech
371 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1bfgadech
372 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1bfgadech
373 if HAS_RICH and rich_markup_mode == "rich": 1bfgadech
374 # This is needed for when we want to export to HTML
375 from . import rich_utils 1bfgadech
377 extra_str = rich_utils.escape_before_html_export(extra_str) 1bfgadech
379 help = f"{help} {extra_str}" if help else f"{extra_str}" 1bfgadech
380 return name, help 1bfgadech
382 def make_metavar(self, ctx: Union[click.Context, None] = None) -> str: 1bfgadech
383 # Modified version of click.core.Argument.make_metavar()
384 # to include Argument name
385 if self.metavar is not None: 1bfgadech
386 var = self.metavar 1bfgadech
387 if not self.required and not var.startswith("["): 1bfgadech
388 var = f"[{var}]" 1bfgadech
389 return var 1bfgadech
390 var = (self.name or "").upper() 1bfgadech
391 if not self.required: 1bfgadech
392 var = f"[{var}]" 1bfgadech
393 # TODO: When deprecating Click < 8.2, remove this
394 signature = inspect.signature(self.type.get_metavar) 1bfgadech
395 if "ctx" in signature.parameters: 1bfgadech
396 # Click >= 8.2
397 type_var = self.type.get_metavar(self, ctx=ctx) # type: ignore[arg-type] 1bfgdech
398 else:
399 # Click < 8.2
400 type_var = self.type.get_metavar(self) # type: ignore[call-arg] 1a
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: 1bfgadech
404 var += f":{type_var}" 1bfgadech
405 if self.nargs != 1: 1bfgadech
406 var += "..." 1bfgadech
407 return var 1bfgadech
409 def value_is_missing(self, value: Any) -> bool: 1bfgadech
410 return _value_is_missing(self, value) 1bfgadech
413class TyperOption(click.core.Option): 1bfgadech
414 def __init__( 1bfgadech
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__( 1bfgadech
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) 1bfgadech
481 self.rich_help_panel = rich_help_panel 1bfgadech
483 def _get_default_string( 1bfgadech
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( 1bfgadech
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( 1bfgadech
498 self, *, ctx: click.Context
499 ) -> Optional[Union[Any, Callable[[], Any]]]:
500 return _extract_default_help_str(self, ctx=ctx) 1bfgadech
502 def make_metavar(self, ctx: Union[click.Context, None] = None) -> str: 1bfgadech
503 signature = inspect.signature(super().make_metavar) 1bfgadech
504 if "ctx" in signature.parameters: 1bfgadech
505 # Click >= 8.2
506 return super().make_metavar(ctx=ctx) # type: ignore[arg-type] 1bfgdech
507 # Click < 8.2
508 return super().make_metavar() # type: ignore[call-arg] 1a
510 def get_help_record(self, ctx: click.Context) -> Optional[tuple[str, str]]: 1bfgadech
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: 1bfgadech
515 return None 1bfgadech
517 any_prefix_is_slash = False 1bfgadech
519 def _write_opts(opts: Sequence[str]) -> str: 1bfgadech
520 nonlocal any_prefix_is_slash
522 rv, any_slashes = click.formatting.join_options(opts) 1bfgadech
524 if any_slashes: 1bfgadech
525 any_prefix_is_slash = True 1bfgadech
527 if not self.is_flag and not self.count: 1bfgadech
528 rv += f" {self.make_metavar(ctx=ctx)}" 1bfgadech
530 return rv 1bfgadech
532 rv = [_write_opts(self.opts)] 1bfgadech
534 if self.secondary_opts: 1bfgadech
535 rv.append(_write_opts(self.secondary_opts)) 1bfgadech
537 help = self.help or "" 1bfgadech
538 extra = [] 1bfgadech
540 if self.show_envvar: 1bfgadech
541 envvar = self.envvar 1bfgadech
543 if envvar is None: 1bfgadech
544 if ( 1bac
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()}" 1bfgadech
551 if envvar is not None: 1bfgadech
552 var_str = ( 1bfgadech
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)) 1bfgadech
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) 1bfgadech
562 # Typer override end
564 show_default_is_str = isinstance(self.show_default, str) 1bfgadech
566 if show_default_is_str or ( 1bfgadech
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( 1bfgadech
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: 1bfgadech
578 extra.append(_("default: {default}").format(default=default_string)) 1bfgadech
580 if isinstance(self.type, click.types._NumberRangeBase): 1bfgadech
581 range_str = self.type._describe_range() 1bfgadech
583 if range_str: 1bfgadech
584 extra.append(range_str) 1bfgadech
586 if self.required: 1bfgadech
587 extra.append(_("required")) 1bfgadech
589 if extra: 1bfgadech
590 extra_str = "; ".join(extra) 1bfgadech
591 extra_str = f"[{extra_str}]" 1bfgadech
592 rich_markup_mode = None 1bfgadech
593 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1bfgadech
594 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1bfgadech
595 if HAS_RICH and rich_markup_mode == "rich": 1bfgadech
596 # This is needed for when we want to export to HTML
597 from . import rich_utils 1bfgadech
599 extra_str = rich_utils.escape_before_html_export(extra_str) 1bfgadech
601 help = f"{help} {extra_str}" if help else f"{extra_str}" 1bfgadech
603 return ("; " if any_prefix_is_slash else " / ").join(rv), help 1bfgadech
605 def value_is_missing(self, value: Any) -> bool: 1bfgadech
606 return _value_is_missing(self, value) 1bfgadech
609def _value_is_missing(param: click.Parameter, value: Any) -> bool: 1bfgadech
610 if value is None: 1bfgadech
611 return True 1bfgadech
613 # Click 8.3 and beyond
614 # if value is UNSET:
615 # return True
617 if (param.nargs != 1 or param.multiple) and value == (): 1bfgadech
618 return True # pragma: no cover
620 return False 1bfgadech
623def _typer_format_options( 1bfgadech
624 self: click.core.Command, *, ctx: click.Context, formatter: click.HelpFormatter
625) -> None:
626 args = [] 1bfgadech
627 opts = [] 1bfgadech
628 for param in self.get_params(ctx): 1bfgadech
629 rv = param.get_help_record(ctx) 1bfgadech
630 if rv is not None: 1bfgadech
631 if param.param_type_name == "argument": 1bfgadech
632 args.append(rv) 1bfgadech
633 elif param.param_type_name == "option": 1bfgadech
634 opts.append(rv) 1bfgadech
636 if args: 1bfgadech
637 with formatter.section(_("Arguments")): 1bfgadech
638 formatter.write_dl(args) 1bfgadech
639 if opts: 1bfgadech
640 with formatter.section(_("Options")): 1bfgadech
641 formatter.write_dl(opts) 1bfgadech
644def _typer_main_shell_completion( 1bfgadech
645 self: click.core.Command,
646 *,
647 ctx_args: MutableMapping[str, Any],
648 prog_name: str,
649 complete_var: Optional[str] = None,
650) -> None:
651 if complete_var is None: 1bfgadech
652 complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() 1bfgadech
654 instruction = os.environ.get(complete_var) 1bfgadech
656 if not instruction: 1bfgadech
657 return 1bfgadech
659 from .completion import shell_complete 1bfgadech
661 rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) 1bfgadech
662 sys.exit(rv) 1bfgadech
665class TyperCommand(click.core.Command): 1bfgadech
666 def __init__( 1bfgadech
667 self,
668 name: Optional[str],
669 *,
670 context_settings: Optional[dict[str, Any]] = None,
671 callback: Optional[Callable[..., Any]] = None,
672 params: Optional[list[click.Parameter]] = None,
673 help: Optional[str] = None,
674 epilog: Optional[str] = None,
675 short_help: Optional[str] = None,
676 options_metavar: Optional[str] = "[OPTIONS]",
677 add_help_option: bool = True,
678 no_args_is_help: bool = False,
679 hidden: bool = False,
680 deprecated: bool = False,
681 # Rich settings
682 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
683 rich_help_panel: Union[str, None] = None,
684 ) -> None:
685 super().__init__( 1bfgadech
686 name=name,
687 context_settings=context_settings,
688 callback=callback,
689 params=params,
690 help=help,
691 epilog=epilog,
692 short_help=short_help,
693 options_metavar=options_metavar,
694 add_help_option=add_help_option,
695 no_args_is_help=no_args_is_help,
696 hidden=hidden,
697 deprecated=deprecated,
698 )
699 self.rich_markup_mode: MarkupMode = rich_markup_mode 1bfgadech
700 self.rich_help_panel = rich_help_panel 1bfgadech
702 def format_options( 1bfgadech
703 self, ctx: click.Context, formatter: click.HelpFormatter
704 ) -> None:
705 _typer_format_options(self, ctx=ctx, formatter=formatter) 1bfgadech
707 def _main_shell_completion( 1bfgadech
708 self,
709 ctx_args: MutableMapping[str, Any],
710 prog_name: str,
711 complete_var: Optional[str] = None,
712 ) -> None:
713 _typer_main_shell_completion( 1bfgadech
714 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
715 )
717 def main( 1bfgadech
718 self,
719 args: Optional[Sequence[str]] = None,
720 prog_name: Optional[str] = None,
721 complete_var: Optional[str] = None,
722 standalone_mode: bool = True,
723 windows_expand_args: bool = True,
724 **extra: Any,
725 ) -> Any:
726 return _main( 1bfgadech
727 self,
728 args=args,
729 prog_name=prog_name,
730 complete_var=complete_var,
731 standalone_mode=standalone_mode,
732 windows_expand_args=windows_expand_args,
733 rich_markup_mode=self.rich_markup_mode,
734 **extra,
735 )
737 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1bfgadech
738 if not HAS_RICH or self.rich_markup_mode is None: 1bfgadech
739 if not hasattr(ctx, "obj") or ctx.obj is None: 1bfgadech
740 ctx.ensure_object(dict) 1bfgadech
741 if isinstance(ctx.obj, dict): 1bfgadech
742 ctx.obj[MARKUP_MODE_KEY] = self.rich_markup_mode 1bfgadech
743 return super().format_help(ctx, formatter) 1bfgadech
744 from . import rich_utils 1bfgadech
746 return rich_utils.rich_format_help( 1bfgadech
747 obj=self,
748 ctx=ctx,
749 markup_mode=self.rich_markup_mode,
750 )
753class TyperGroup(click.core.Group): 1bfgadech
754 def __init__( 1bfgadech
755 self,
756 *,
757 name: Optional[str] = None,
758 commands: Optional[
759 Union[dict[str, click.Command], Sequence[click.Command]]
760 ] = None,
761 # Rich settings
762 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
763 rich_help_panel: Union[str, None] = None,
764 suggest_commands: bool = True,
765 **attrs: Any,
766 ) -> None:
767 super().__init__(name=name, commands=commands, **attrs) 1bfgadech
768 self.rich_markup_mode: MarkupMode = rich_markup_mode 1bfgadech
769 self.rich_help_panel = rich_help_panel 1bfgadech
770 self.suggest_commands = suggest_commands 1bfgadech
772 def format_options( 1bfgadech
773 self, ctx: click.Context, formatter: click.HelpFormatter
774 ) -> None:
775 _typer_format_options(self, ctx=ctx, formatter=formatter) 1bfgadech
776 self.format_commands(ctx, formatter) 1bfgadech
778 def _main_shell_completion( 1bfgadech
779 self,
780 ctx_args: MutableMapping[str, Any],
781 prog_name: str,
782 complete_var: Optional[str] = None,
783 ) -> None:
784 _typer_main_shell_completion( 1bfgadech
785 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
786 )
788 def resolve_command( 1bfgadech
789 self, ctx: click.Context, args: list[str]
790 ) -> tuple[Optional[str], Optional[click.Command], list[str]]:
791 try: 1bfgadech
792 return super().resolve_command(ctx, args) 1bfgadech
793 except click.UsageError as e: 1bfgadech
794 if self.suggest_commands: 1bfgadech
795 available_commands = list(self.commands.keys()) 1bfgadech
796 if available_commands and args: 1bfgadech
797 typo = args[0] 1bfgadech
798 matches = get_close_matches(typo, available_commands) 1bfgadech
799 if matches: 1bfgadech
800 suggestions = ", ".join(f"{m!r}" for m in matches) 1bfgadech
801 message = e.message.rstrip(".") 1bfgadech
802 e.message = f"{message}. Did you mean {suggestions}?" 1bfgadech
803 raise 1bfgadech
805 def main( 1bfgadech
806 self,
807 args: Optional[Sequence[str]] = None,
808 prog_name: Optional[str] = None,
809 complete_var: Optional[str] = None,
810 standalone_mode: bool = True,
811 windows_expand_args: bool = True,
812 **extra: Any,
813 ) -> Any:
814 return _main( 1bfgadech
815 self,
816 args=args,
817 prog_name=prog_name,
818 complete_var=complete_var,
819 standalone_mode=standalone_mode,
820 windows_expand_args=windows_expand_args,
821 rich_markup_mode=self.rich_markup_mode,
822 **extra,
823 )
825 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1bfgadech
826 if not HAS_RICH or self.rich_markup_mode is None: 1bfgadech
827 return super().format_help(ctx, formatter) 1bfgadech
828 from . import rich_utils 1bfgadech
830 return rich_utils.rich_format_help( 1bfgadech
831 obj=self,
832 ctx=ctx,
833 markup_mode=self.rich_markup_mode,
834 )
836 def list_commands(self, ctx: click.Context) -> list[str]: 1bfgadech
837 """Returns a list of subcommand names.
838 Note that in Click's Group class, these are sorted.
839 In Typer, we wish to maintain the original order of creation (cf Issue #933)"""
840 return [n for n, c in self.commands.items()] 1bfgadech