Coverage for typer / main.py: 100%
490 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 inspect 1acdefbg
2import os 1acdefbg
3import platform 1acdefbg
4import shutil 1acdefbg
5import subprocess 1acdefbg
6import sys 1acdefbg
7import traceback 1acdefbg
8from collections.abc import Callable, Sequence 1acdefbg
9from datetime import datetime 1acdefbg
10from enum import Enum 1acdefbg
11from functools import update_wrapper 1acdefbg
12from pathlib import Path 1acdefbg
13from traceback import FrameSummary, StackSummary 1acdefbg
14from types import TracebackType 1acdefbg
15from typing import Annotated, Any 1acdefbg
16from uuid import UUID 1acdefbg
18import click 1acdefbg
19from annotated_doc import Doc 1acdefbg
20from typer._types import TyperChoice 1acdefbg
22from ._typing import get_args, get_origin, is_literal_type, is_union, literal_values 1acdefbg
23from .completion import get_completion_inspect_parameters 1acdefbg
24from .core import ( 1acdefbg
25 DEFAULT_MARKUP_MODE,
26 HAS_RICH,
27 MarkupMode,
28 TyperArgument,
29 TyperCommand,
30 TyperGroup,
31 TyperOption,
32)
33from .models import ( 1acdefbg
34 AnyType,
35 ArgumentInfo,
36 CommandFunctionType,
37 CommandInfo,
38 Default,
39 DefaultPlaceholder,
40 DeveloperExceptionConfig,
41 FileBinaryRead,
42 FileBinaryWrite,
43 FileText,
44 FileTextWrite,
45 NoneType,
46 OptionInfo,
47 ParameterInfo,
48 ParamMeta,
49 Required,
50 TyperInfo,
51 TyperPath,
52)
53from .utils import get_params_from_function 1acdefbg
55_original_except_hook = sys.excepthook 1acdefbg
56_typer_developer_exception_attr_name = "__typer_developer_exception__" 1acdefbg
59def except_hook( 1acdefbg
60 exc_type: type[BaseException], exc_value: BaseException, tb: TracebackType | None
61) -> None:
62 exception_config: DeveloperExceptionConfig | None = getattr( 1acdefbg
63 exc_value, _typer_developer_exception_attr_name, None
64 )
65 standard_traceback = os.getenv( 1acdefbg
66 "TYPER_STANDARD_TRACEBACK", os.getenv("_TYPER_STANDARD_TRACEBACK")
67 )
68 if ( 1ab
69 standard_traceback
70 or not exception_config
71 or not exception_config.pretty_exceptions_enable
72 ):
73 _original_except_hook(exc_type, exc_value, tb) 1acdefbg
74 return 1acdefbg
75 typer_path = os.path.dirname(__file__) 1acdefbg
76 click_path = os.path.dirname(click.__file__) 1acdefbg
77 internal_dir_names = [typer_path, click_path] 1acdefbg
78 exc = exc_value 1acdefbg
79 if HAS_RICH: 1acdefbg
80 from . import rich_utils 1acdefbg
82 rich_tb = rich_utils.get_traceback(exc, exception_config, internal_dir_names) 1acdefbg
83 console_stderr = rich_utils._get_rich_console(stderr=True) 1acdefbg
84 console_stderr.print(rich_tb) 1acdefbg
85 return 1acdefbg
86 tb_exc = traceback.TracebackException.from_exception(exc) 1acdefbg
87 stack: list[FrameSummary] = [] 1acdefbg
88 for frame in tb_exc.stack: 1acdefbg
89 if any(frame.filename.startswith(path) for path in internal_dir_names): 1acdefbg
90 if not exception_config.pretty_exceptions_short: 1acdefbg
91 # Hide the line for internal libraries, Typer and Click
92 stack.append( 1acdefbg
93 traceback.FrameSummary(
94 filename=frame.filename,
95 lineno=frame.lineno,
96 name=frame.name,
97 line="",
98 )
99 )
100 else:
101 stack.append(frame) 1acdefbg
102 # Type ignore ref: https://github.com/python/typeshed/pull/8244
103 final_stack_summary = StackSummary.from_list(stack) 1acdefbg
104 tb_exc.stack = final_stack_summary 1acdefbg
105 for line in tb_exc.format(): 1acdefbg
106 print(line, file=sys.stderr) 1acdefbg
107 return 1acdefbg
110def get_install_completion_arguments() -> tuple[click.Parameter, click.Parameter]: 1acdefbg
111 install_param, show_param = get_completion_inspect_parameters() 1acdefbg
112 click_install_param, _ = get_click_param(install_param) 1acdefbg
113 click_show_param, _ = get_click_param(show_param) 1acdefbg
114 return click_install_param, click_show_param 1acdefbg
117class Typer: 1acdefbg
118 """
119 `Typer` main class, the main entrypoint to use Typer.
121 Read more in the
122 [Typer docs for First Steps](https://typer.tiangolo.com/tutorial/typer-app/).
124 ## Example
126 ```python
127 import typer
129 app = typer.Typer()
130 ```
131 """
133 def __init__( 1acdefbg
134 self,
135 *,
136 name: Annotated[
137 str | None,
138 Doc(
139 """
140 The name of this application.
141 Mostly used to set the name for [subcommands](https://typer.tiangolo.com/tutorial/subcommands/), in which case it can be overridden by `add_typer(name=...)`.
143 **Example**
145 ```python
146 import typer
148 app = typer.Typer(name="users")
149 ```
150 """
151 ),
152 ] = Default(None),
153 cls: Annotated[
154 type[TyperGroup] | None,
155 Doc(
156 """
157 The class of this app. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`.
158 Otherwise, should be a subtype of `TyperGroup`.
160 **Example**
162 ```python
163 import typer
165 app = typer.Typer(cls=TyperGroup)
166 ```
167 """
168 ),
169 ] = Default(None),
170 invoke_without_command: Annotated[
171 bool,
172 Doc(
173 """
174 By setting this to `True`, you can make sure a callback is executed even when no subcommand is provided.
176 **Example**
178 ```python
179 import typer
181 app = typer.Typer(invoke_without_command=True)
182 ```
183 """
184 ),
185 ] = Default(False),
186 no_args_is_help: Annotated[
187 bool,
188 Doc(
189 """
190 If this is set to `True`, running a command without any arguments will automatically show the help page.
192 **Example**
194 ```python
195 import typer
197 app = typer.Typer(no_args_is_help=True)
198 ```
199 """
200 ),
201 ] = Default(False),
202 subcommand_metavar: Annotated[
203 str | None,
204 Doc(
205 """
206 **Note**: you probably shouldn't use this parameter, it is inherited
207 from Click and supported for compatibility.
209 ---
211 How to represent the subcommand argument in help.
212 """
213 ),
214 ] = Default(None),
215 chain: Annotated[
216 bool,
217 Doc(
218 """
219 **Note**: you probably shouldn't use this parameter, it is inherited
220 from Click and supported for compatibility.
222 ---
224 Allow passing more than one subcommand argument.
225 """
226 ),
227 ] = Default(False),
228 result_callback: Annotated[
229 Callable[..., Any] | None,
230 Doc(
231 """
232 **Note**: you probably shouldn't use this parameter, it is inherited
233 from Click and supported for compatibility.
235 ---
237 A function to call after the group's and subcommand's callbacks.
238 """
239 ),
240 ] = Default(None),
241 # Command
242 context_settings: Annotated[
243 dict[Any, Any] | None,
244 Doc(
245 """
246 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/).
247 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context).
249 **Example**
251 ```python
252 import typer
254 app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
255 ```
256 """
257 ),
258 ] = Default(None),
259 callback: Annotated[
260 Callable[..., Any] | None,
261 Doc(
262 """
263 Add a callback to the main Typer app. Can be overridden with `@app.callback()`.
264 See [the tutorial about callbacks](https://typer.tiangolo.com/tutorial/commands/callback/) for more details.
266 **Example**
268 ```python
269 import typer
271 def callback():
272 print("Running a command")
274 app = typer.Typer(callback=callback)
275 ```
276 """
277 ),
278 ] = Default(None),
279 help: Annotated[
280 str | None,
281 Doc(
282 """
283 Help text for the main Typer app.
284 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help,
285 and which one takes priority.
287 **Example**
289 ```python
290 import typer
292 app = typer.Typer(help="Some help.")
293 ```
294 """
295 ),
296 ] = Default(None),
297 epilog: Annotated[
298 str | None,
299 Doc(
300 """
301 Text that will be printed right after the help text.
303 **Example**
305 ```python
306 import typer
308 app = typer.Typer(epilog="May the force be with you")
309 ```
310 """
311 ),
312 ] = Default(None),
313 short_help: Annotated[
314 str | None,
315 Doc(
316 """
317 A shortened version of the help text that can be used e.g. in the help table listing subcommands.
318 When not defined, the normal `help` text will be used instead.
320 **Example**
322 ```python
323 import typer
325 app = typer.Typer(help="A lot of explanation about user management", short_help="user management")
326 ```
327 """
328 ),
329 ] = Default(None),
330 options_metavar: Annotated[
331 str,
332 Doc(
333 """
334 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`.
335 Set `options_metavar` to change this into a different string.
337 **Example**
339 ```python
340 import typer
342 app = typer.Typer(options_metavar="[OPTS]")
343 ```
344 """
345 ),
346 ] = Default("[OPTIONS]"),
347 add_help_option: Annotated[
348 bool,
349 Doc(
350 """
351 **Note**: you probably shouldn't use this parameter, it is inherited
352 from Click and supported for compatibility.
354 ---
356 By default each command registers a `--help` option. This can be disabled by this parameter.
357 """
358 ),
359 ] = Default(True),
360 hidden: Annotated[
361 bool,
362 Doc(
363 """
364 Hide this command from help outputs. `False` by default.
366 **Example**
368 ```python
369 import typer
371 app = typer.Typer(hidden=True)
372 ```
373 """
374 ),
375 ] = Default(False),
376 deprecated: Annotated[
377 bool,
378 Doc(
379 """
380 Mark this command as being deprecated in the help text. `False` by default.
382 **Example**
384 ```python
385 import typer
387 app = typer.Typer(deprecated=True)
388 ```
389 """
390 ),
391 ] = Default(False),
392 add_completion: Annotated[
393 bool,
394 Doc(
395 """
396 Toggle whether or not to add the `--install-completion` and `--show-completion` options to the app.
397 Set to `True` by default.
399 **Example**
401 ```python
402 import typer
404 app = typer.Typer(add_completion=False)
405 ```
406 """
407 ),
408 ] = True,
409 # Rich settings
410 rich_markup_mode: Annotated[
411 MarkupMode,
412 Doc(
413 """
414 Enable markup text if you have Rich installed. This can be set to `"markdown"`, `"rich"`, or `None`.
415 By default, `rich_markup_mode` is `None` if Rich is not installed, and `"rich"` if it is installed.
416 See [the tutorial on help formatting](https://typer.tiangolo.com/tutorial/commands/help/#rich-markdown-and-markup) for more information.
418 **Example**
420 ```python
421 import typer
423 app = typer.Typer(rich_markup_mode="rich")
424 ```
425 """
426 ),
427 ] = DEFAULT_MARKUP_MODE,
428 rich_help_panel: Annotated[
429 str | None,
430 Doc(
431 """
432 Set the panel name of the command when the help is printed with Rich.
434 **Example**
436 ```python
437 import typer
439 app = typer.Typer(rich_help_panel="Utils and Configs")
440 ```
441 """
442 ),
443 ] = Default(None),
444 suggest_commands: Annotated[
445 bool,
446 Doc(
447 """
448 As of version 0.20.0, Typer provides [support for mistyped command names](https://typer.tiangolo.com/tutorial/commands/help/#suggest-commands) by printing helpful suggestions.
449 You can turn this setting off with `suggest_commands`:
451 **Example**
453 ```python
454 import typer
456 app = typer.Typer(suggest_commands=False)
457 ```
458 """
459 ),
460 ] = True,
461 pretty_exceptions_enable: Annotated[
462 bool,
463 Doc(
464 """
465 If you want to disable [pretty exceptions with Rich](https://typer.tiangolo.com/tutorial/exceptions/#exceptions-with-rich),
466 you can set `pretty_exceptions_enable` to `False`. When doing so, you will see the usual standard exception trace.
468 **Example**
470 ```python
471 import typer
473 app = typer.Typer(pretty_exceptions_enable=False)
474 ```
475 """
476 ),
477 ] = True,
478 pretty_exceptions_show_locals: Annotated[
479 bool,
480 Doc(
481 """
482 If Rich is installed, [error messages](https://typer.tiangolo.com/tutorial/exceptions/#exceptions-and-errors)
483 will be nicely printed.
485 If you set `pretty_exceptions_show_locals=True` it will also include the values of local variables for easy debugging.
487 However, if such a variable contains delicate information, you should consider leaving `pretty_exceptions_show_locals=False`
488 (the default) to `False` to enhance security.
490 **Example**
492 ```python
493 import typer
495 app = typer.Typer(pretty_exceptions_show_locals=True)
496 ```
497 """
498 ),
499 ] = False,
500 pretty_exceptions_short: Annotated[
501 bool,
502 Doc(
503 """
504 By default, [pretty exceptions formatted with Rich](https://typer.tiangolo.com/tutorial/exceptions/#exceptions-with-rich) hide the long stack trace.
505 If you want to show the full trace instead, you can set the parameter `pretty_exceptions_short` to `False`:
507 **Example**
509 ```python
510 import typer
512 app = typer.Typer(pretty_exceptions_short=False)
513 ```
514 """
515 ),
516 ] = True,
517 ):
518 self._add_completion = add_completion 1acdefbg
519 self.rich_markup_mode: MarkupMode = rich_markup_mode 1acdefbg
520 self.rich_help_panel = rich_help_panel 1acdefbg
521 self.suggest_commands = suggest_commands 1acdefbg
522 self.pretty_exceptions_enable = pretty_exceptions_enable 1acdefbg
523 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1acdefbg
524 self.pretty_exceptions_short = pretty_exceptions_short 1acdefbg
525 self.info = TyperInfo( 1acdefbg
526 name=name,
527 cls=cls,
528 invoke_without_command=invoke_without_command,
529 no_args_is_help=no_args_is_help,
530 subcommand_metavar=subcommand_metavar,
531 chain=chain,
532 result_callback=result_callback,
533 context_settings=context_settings,
534 callback=callback,
535 help=help,
536 epilog=epilog,
537 short_help=short_help,
538 options_metavar=options_metavar,
539 add_help_option=add_help_option,
540 hidden=hidden,
541 deprecated=deprecated,
542 )
543 self.registered_groups: list[TyperInfo] = [] 1acdefbg
544 self.registered_commands: list[CommandInfo] = [] 1acdefbg
545 self.registered_callback: TyperInfo | None = None 1acdefbg
547 def callback( 1acdefbg
548 self,
549 *,
550 cls: Annotated[
551 type[TyperGroup] | None,
552 Doc(
553 """
554 The class of this app. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`.
555 Otherwise, should be a subtype of `TyperGroup`.
556 """
557 ),
558 ] = Default(None),
559 invoke_without_command: Annotated[
560 bool,
561 Doc(
562 """
563 By setting this to `True`, you can make sure a callback is executed even when no subcommand is provided.
564 """
565 ),
566 ] = Default(False),
567 no_args_is_help: Annotated[
568 bool,
569 Doc(
570 """
571 If this is set to `True`, running a command without any arguments will automatically show the help page.
572 """
573 ),
574 ] = Default(False),
575 subcommand_metavar: Annotated[
576 str | None,
577 Doc(
578 """
579 **Note**: you probably shouldn't use this parameter, it is inherited
580 from Click and supported for compatibility.
582 ---
584 How to represent the subcommand argument in help.
585 """
586 ),
587 ] = Default(None),
588 chain: Annotated[
589 bool,
590 Doc(
591 """
592 **Note**: you probably shouldn't use this parameter, it is inherited
593 from Click and supported for compatibility.
595 ---
597 Allow passing more than one subcommand argument.
598 """
599 ),
600 ] = Default(False),
601 result_callback: Annotated[
602 Callable[..., Any] | None,
603 Doc(
604 """
605 **Note**: you probably shouldn't use this parameter, it is inherited
606 from Click and supported for compatibility.
608 ---
610 A function to call after the group's and subcommand's callbacks.
611 """
612 ),
613 ] = Default(None),
614 # Command
615 context_settings: Annotated[
616 dict[Any, Any] | None,
617 Doc(
618 """
619 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/).
620 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context).
621 """
622 ),
623 ] = Default(None),
624 help: Annotated[
625 str | None,
626 Doc(
627 """
628 Help text for the command.
629 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help,
630 and which one takes priority.
631 """
632 ),
633 ] = Default(None),
634 epilog: Annotated[
635 str | None,
636 Doc(
637 """
638 Text that will be printed right after the help text.
639 """
640 ),
641 ] = Default(None),
642 short_help: Annotated[
643 str | None,
644 Doc(
645 """
646 A shortened version of the help text that can be used e.g. in the help table listing subcommands.
647 When not defined, the normal `help` text will be used instead.
648 """
649 ),
650 ] = Default(None),
651 options_metavar: Annotated[
652 str | None,
653 Doc(
654 """
655 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`.
656 Set `options_metavar` to change this into a different string. When `None`, the default value will be used.
657 """
658 ),
659 ] = Default(None),
660 add_help_option: Annotated[
661 bool,
662 Doc(
663 """
664 **Note**: you probably shouldn't use this parameter, it is inherited
665 from Click and supported for compatibility.
667 ---
669 By default each command registers a `--help` option. This can be disabled by this parameter.
670 """
671 ),
672 ] = Default(True),
673 hidden: Annotated[
674 bool,
675 Doc(
676 """
677 Hide this command from help outputs. `False` by default.
678 """
679 ),
680 ] = Default(False),
681 deprecated: Annotated[
682 bool,
683 Doc(
684 """
685 Mark this command as deprecated in the help text. `False` by default.
686 """
687 ),
688 ] = Default(False),
689 # Rich settings
690 rich_help_panel: Annotated[
691 str | None,
692 Doc(
693 """
694 Set the panel name of the command when the help is printed with Rich.
695 """
696 ),
697 ] = Default(None),
698 ) -> Callable[[CommandFunctionType], CommandFunctionType]:
699 """
700 Using the decorator `@app.callback`, you can declare the CLI parameters for the main CLI application.
702 Read more in the
703 [Typer docs for Callbacks](https://typer.tiangolo.com/tutorial/commands/callback/).
705 ## Example
707 ```python
708 import typer
710 app = typer.Typer()
711 state = {"verbose": False}
713 @app.callback()
714 def main(verbose: bool = False):
715 if verbose:
716 print("Will write verbose output")
717 state["verbose"] = True
719 @app.command()
720 def delete(username: str):
721 # define subcommand
722 ...
723 ```
724 """
726 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1acdefbg
727 self.registered_callback = TyperInfo( 1acdefbg
728 cls=cls,
729 invoke_without_command=invoke_without_command,
730 no_args_is_help=no_args_is_help,
731 subcommand_metavar=subcommand_metavar,
732 chain=chain,
733 result_callback=result_callback,
734 context_settings=context_settings,
735 callback=f,
736 help=help,
737 epilog=epilog,
738 short_help=short_help,
739 options_metavar=(
740 options_metavar or self._info_val_str("options_metavar")
741 ),
742 add_help_option=add_help_option,
743 hidden=hidden,
744 deprecated=deprecated,
745 rich_help_panel=rich_help_panel,
746 )
747 return f 1acdefbg
749 return decorator 1acdefbg
751 def command( 1acdefbg
752 self,
753 name: Annotated[
754 str | None,
755 Doc(
756 """
757 The name of this command.
758 """
759 ),
760 ] = None,
761 *,
762 cls: Annotated[
763 type[TyperCommand] | None,
764 Doc(
765 """
766 The class of this command. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`.
767 Otherwise, should be a subtype of `TyperCommand`.
768 """
769 ),
770 ] = None,
771 context_settings: Annotated[
772 dict[Any, Any] | None,
773 Doc(
774 """
775 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/).
776 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context).
777 """
778 ),
779 ] = None,
780 help: Annotated[
781 str | None,
782 Doc(
783 """
784 Help text for the command.
785 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help,
786 and which one takes priority.
787 """
788 ),
789 ] = None,
790 epilog: Annotated[
791 str | None,
792 Doc(
793 """
794 Text that will be printed right after the help text.
795 """
796 ),
797 ] = None,
798 short_help: Annotated[
799 str | None,
800 Doc(
801 """
802 A shortened version of the help text that can be used e.g. in the help table listing subcommands.
803 When not defined, the normal `help` text will be used instead.
804 """
805 ),
806 ] = None,
807 options_metavar: Annotated[
808 str | None,
809 Doc(
810 """
811 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`.
812 Set `options_metavar` to change this into a different string. When `None`, the default value will be used.
813 """
814 ),
815 ] = Default(None),
816 add_help_option: Annotated[
817 bool,
818 Doc(
819 """
820 **Note**: you probably shouldn't use this parameter, it is inherited
821 from Click and supported for compatibility.
823 ---
825 By default each command registers a `--help` option. This can be disabled by this parameter.
826 """
827 ),
828 ] = True,
829 no_args_is_help: Annotated[
830 bool,
831 Doc(
832 """
833 If this is set to `True`, running a command without any arguments will automatically show the help page.
834 """
835 ),
836 ] = False,
837 hidden: Annotated[
838 bool,
839 Doc(
840 """
841 Hide this command from help outputs. `False` by default.
842 """
843 ),
844 ] = False,
845 deprecated: Annotated[
846 bool,
847 Doc(
848 """
849 Mark this command as deprecated in the help outputs. `False` by default.
850 """
851 ),
852 ] = False,
853 # Rich settings
854 rich_help_panel: Annotated[
855 str | None,
856 Doc(
857 """
858 Set the panel name of the command when the help is printed with Rich.
859 """
860 ),
861 ] = Default(None),
862 ) -> Callable[[CommandFunctionType], CommandFunctionType]:
863 """
864 Using the decorator `@app.command`, you can define a subcommand of the previously defined Typer app.
866 Read more in the
867 [Typer docs for Commands](https://typer.tiangolo.com/tutorial/commands/).
869 ## Example
871 ```python
872 import typer
874 app = typer.Typer()
876 @app.command()
877 def create():
878 print("Creating user: Hiro Hamada")
880 @app.command()
881 def delete():
882 print("Deleting user: Hiro Hamada")
883 ```
884 """
885 if cls is None: 1acdefbg
886 cls = TyperCommand 1acdefbg
888 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1acdefbg
889 self.registered_commands.append( 1acdefbg
890 CommandInfo(
891 name=name,
892 cls=cls,
893 context_settings=context_settings,
894 callback=f,
895 help=help,
896 epilog=epilog,
897 short_help=short_help,
898 options_metavar=(
899 options_metavar or self._info_val_str("options_metavar")
900 ),
901 add_help_option=add_help_option,
902 no_args_is_help=no_args_is_help,
903 hidden=hidden,
904 deprecated=deprecated,
905 # Rich settings
906 rich_help_panel=rich_help_panel,
907 )
908 )
909 return f 1acdefbg
911 return decorator 1acdefbg
913 def add_typer( 1acdefbg
914 self,
915 typer_instance: "Typer",
916 *,
917 name: Annotated[
918 str | None,
919 Doc(
920 """
921 The name of this subcommand.
922 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's name,
923 and which one takes priority.
924 """
925 ),
926 ] = Default(None),
927 cls: Annotated[
928 type[TyperGroup] | None,
929 Doc(
930 """
931 The class of this subcommand. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`.
932 Otherwise, should be a subtype of `TyperGroup`.
933 """
934 ),
935 ] = Default(None),
936 invoke_without_command: Annotated[
937 bool,
938 Doc(
939 """
940 By setting this to `True`, you can make sure a callback is executed even when no subcommand is provided.
941 """
942 ),
943 ] = Default(False),
944 no_args_is_help: Annotated[
945 bool,
946 Doc(
947 """
948 If this is set to `True`, running a command without any arguments will automatically show the help page.
949 """
950 ),
951 ] = Default(False),
952 subcommand_metavar: Annotated[
953 str | None,
954 Doc(
955 """
956 **Note**: you probably shouldn't use this parameter, it is inherited
957 from Click and supported for compatibility.
959 ---
961 How to represent the subcommand argument in help.
962 """
963 ),
964 ] = Default(None),
965 chain: Annotated[
966 bool,
967 Doc(
968 """
969 **Note**: you probably shouldn't use this parameter, it is inherited
970 from Click and supported for compatibility.
972 ---
974 Allow passing more than one subcommand argument.
975 """
976 ),
977 ] = Default(False),
978 result_callback: Annotated[
979 Callable[..., Any] | None,
980 Doc(
981 """
982 **Note**: you probably shouldn't use this parameter, it is inherited
983 from Click and supported for compatibility.
985 ---
987 A function to call after the group's and subcommand's callbacks.
988 """
989 ),
990 ] = Default(None),
991 # Command
992 context_settings: Annotated[
993 dict[Any, Any] | None,
994 Doc(
995 """
996 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/).
997 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context).
998 """
999 ),
1000 ] = Default(None),
1001 callback: Annotated[
1002 Callable[..., Any] | None,
1003 Doc(
1004 """
1005 Add a callback to this app.
1006 See [the tutorial about callbacks](https://typer.tiangolo.com/tutorial/commands/callback/) for more details.
1007 """
1008 ),
1009 ] = Default(None),
1010 help: Annotated[
1011 str | None,
1012 Doc(
1013 """
1014 Help text for the subcommand.
1015 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help,
1016 and which one takes priority.
1017 """
1018 ),
1019 ] = Default(None),
1020 epilog: Annotated[
1021 str | None,
1022 Doc(
1023 """
1024 Text that will be printed right after the help text.
1025 """
1026 ),
1027 ] = Default(None),
1028 short_help: Annotated[
1029 str | None,
1030 Doc(
1031 """
1032 A shortened version of the help text that can be used e.g. in the help table listing subcommands.
1033 When not defined, the normal `help` text will be used instead.
1034 """
1035 ),
1036 ] = Default(None),
1037 options_metavar: Annotated[
1038 str | None,
1039 Doc(
1040 """
1041 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`.
1042 Set `options_metavar` to change this into a different string. When `None`, the default value will be used.
1043 """
1044 ),
1045 ] = Default(None),
1046 add_help_option: Annotated[
1047 bool,
1048 Doc(
1049 """
1050 **Note**: you probably shouldn't use this parameter, it is inherited
1051 from Click and supported for compatibility.
1053 ---
1055 By default each command registers a `--help` option. This can be disabled by this parameter.
1056 """
1057 ),
1058 ] = Default(True),
1059 hidden: Annotated[
1060 bool,
1061 Doc(
1062 """
1063 Hide this command from help outputs. `False` by default.
1064 """
1065 ),
1066 ] = Default(False),
1067 deprecated: Annotated[
1068 bool,
1069 Doc(
1070 """
1071 Mark this command as deprecated in the help outputs. `False` by default.
1072 """
1073 ),
1074 ] = False,
1075 # Rich settings
1076 rich_help_panel: Annotated[
1077 str | None,
1078 Doc(
1079 """
1080 Set the panel name of the command when the help is printed with Rich.
1081 """
1082 ),
1083 ] = Default(None),
1084 ) -> None:
1085 """
1086 Add subcommands to the main app using `app.add_typer()`.
1087 Subcommands may be defined in separate modules, ensuring clean separation of code by functionality.
1089 Read more in the
1090 [Typer docs for SubCommands](https://typer.tiangolo.com/tutorial/subcommands/add-typer/).
1092 ## Example
1094 ```python
1095 import typer
1097 from .add import app as add_app
1098 from .delete import app as delete_app
1100 app = typer.Typer()
1102 app.add_typer(add_app)
1103 app.add_typer(delete_app)
1104 ```
1105 """
1106 self.registered_groups.append( 1acdefbg
1107 TyperInfo(
1108 typer_instance,
1109 name=name,
1110 cls=cls,
1111 invoke_without_command=invoke_without_command,
1112 no_args_is_help=no_args_is_help,
1113 subcommand_metavar=subcommand_metavar,
1114 chain=chain,
1115 result_callback=result_callback,
1116 context_settings=context_settings,
1117 callback=callback,
1118 help=help,
1119 epilog=epilog,
1120 short_help=short_help,
1121 options_metavar=(
1122 options_metavar or self._info_val_str("options_metavar")
1123 ),
1124 add_help_option=add_help_option,
1125 hidden=hidden,
1126 deprecated=deprecated,
1127 rich_help_panel=rich_help_panel,
1128 )
1129 )
1131 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1acdefbg
1132 if sys.excepthook != except_hook: 1acdefbg
1133 sys.excepthook = except_hook 1acdefbg
1134 try: 1acdefbg
1135 return get_command(self)(*args, **kwargs) 1acdefbg
1136 except Exception as e: 1acdefbg
1137 # Set a custom attribute to tell the hook to show nice exceptions for user
1138 # code. An alternative/first implementation was a custom exception with
1139 # raise custom_exc from e
1140 # but that means the last error shown is the custom exception, not the
1141 # actual error. This trick improves developer experience by showing the
1142 # actual error last.
1143 setattr( 1acdefbg
1144 e,
1145 _typer_developer_exception_attr_name,
1146 DeveloperExceptionConfig(
1147 pretty_exceptions_enable=self.pretty_exceptions_enable,
1148 pretty_exceptions_show_locals=self.pretty_exceptions_show_locals,
1149 pretty_exceptions_short=self.pretty_exceptions_short,
1150 ),
1151 )
1152 raise e 1acdefbg
1154 def _info_val_str(self, name: str) -> str: 1acdefbg
1155 val = getattr(self.info, name) 1acdefbg
1156 val_str = val.value if isinstance(val, DefaultPlaceholder) else val 1acdefbg
1157 assert isinstance(val_str, str) 1acdefbg
1158 return val_str 1acdefbg
1161def get_group(typer_instance: Typer) -> TyperGroup: 1acdefbg
1162 group = get_group_from_info( 1acdefbg
1163 TyperInfo(typer_instance),
1164 pretty_exceptions_short=typer_instance.pretty_exceptions_short,
1165 rich_markup_mode=typer_instance.rich_markup_mode,
1166 suggest_commands=typer_instance.suggest_commands,
1167 )
1168 return group 1acdefbg
1171def get_command(typer_instance: Typer) -> click.Command: 1acdefbg
1172 if typer_instance._add_completion: 1acdefbg
1173 click_install_param, click_show_param = get_install_completion_arguments() 1acdefbg
1174 if ( 1ab
1175 typer_instance.registered_callback
1176 or typer_instance.info.callback
1177 or typer_instance.registered_groups
1178 or len(typer_instance.registered_commands) > 1
1179 ):
1180 # Create a Group
1181 click_command: click.Command = get_group(typer_instance) 1acdefbg
1182 if typer_instance._add_completion: 1acdefbg
1183 click_command.params.append(click_install_param) 1acdefbg
1184 click_command.params.append(click_show_param) 1acdefbg
1185 return click_command 1acdefbg
1186 elif len(typer_instance.registered_commands) == 1: 1acdefbg
1187 # Create a single Command
1188 single_command = typer_instance.registered_commands[0] 1acdefbg
1190 if not single_command.context_settings and not isinstance( 1acdefbg
1191 typer_instance.info.context_settings, DefaultPlaceholder
1192 ):
1193 single_command.context_settings = typer_instance.info.context_settings 1acdefbg
1195 click_command = get_command_from_info( 1acdefbg
1196 single_command,
1197 pretty_exceptions_short=typer_instance.pretty_exceptions_short,
1198 rich_markup_mode=typer_instance.rich_markup_mode,
1199 )
1200 if typer_instance._add_completion: 1acdefbg
1201 click_command.params.append(click_install_param) 1acdefbg
1202 click_command.params.append(click_show_param) 1acdefbg
1203 return click_command 1acdefbg
1204 raise RuntimeError(
1205 "Could not get a command for this Typer instance"
1206 ) # pragma: no cover
1209def solve_typer_info_help(typer_info: TyperInfo) -> str: 1acdefbg
1210 # Priority 1: Explicit value was set in app.add_typer()
1211 if not isinstance(typer_info.help, DefaultPlaceholder): 1acdefbg
1212 return inspect.cleandoc(typer_info.help or "") 1acdefbg
1213 # Priority 2: Explicit value was set in sub_app.callback()
1214 if typer_info.typer_instance and typer_info.typer_instance.registered_callback: 1acdefbg
1215 callback_help = typer_info.typer_instance.registered_callback.help 1acdefbg
1216 if not isinstance(callback_help, DefaultPlaceholder): 1acdefbg
1217 return inspect.cleandoc(callback_help or "") 1acdefbg
1218 # Priority 3: Explicit value was set in sub_app = typer.Typer()
1219 if typer_info.typer_instance and typer_info.typer_instance.info: 1acdefbg
1220 instance_help = typer_info.typer_instance.info.help 1acdefbg
1221 if not isinstance(instance_help, DefaultPlaceholder): 1acdefbg
1222 return inspect.cleandoc(instance_help or "") 1acdefbg
1223 # Priority 4: Implicit inference from callback docstring in app.add_typer()
1224 if typer_info.callback: 1acdefbg
1225 doc = inspect.getdoc(typer_info.callback) 1acdefbg
1226 if doc: 1acdefbg
1227 return doc 1acdefbg
1228 # Priority 5: Implicit inference from callback docstring in @app.callback()
1229 if typer_info.typer_instance and typer_info.typer_instance.registered_callback: 1acdefbg
1230 callback = typer_info.typer_instance.registered_callback.callback 1acdefbg
1231 if not isinstance(callback, DefaultPlaceholder): 1acdefbg
1232 doc = inspect.getdoc(callback or "") 1acdefbg
1233 if doc: 1acdefbg
1234 return doc 1acdefbg
1235 # Priority 6: Implicit inference from callback docstring in typer.Typer()
1236 if typer_info.typer_instance and typer_info.typer_instance.info: 1acdefbg
1237 instance_callback = typer_info.typer_instance.info.callback 1acdefbg
1238 if not isinstance(instance_callback, DefaultPlaceholder): 1acdefbg
1239 doc = inspect.getdoc(instance_callback) 1acdefbg
1240 if doc: 1acdefbg
1241 return doc 1acdefbg
1242 # Value not set, use the default
1243 return typer_info.help.value 1acdefbg
1246def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1acdefbg
1247 values: dict[str, Any] = {} 1acdefbg
1248 for name, value in typer_info.__dict__.items(): 1acdefbg
1249 # Priority 1: Value was set in app.add_typer()
1250 if not isinstance(value, DefaultPlaceholder): 1acdefbg
1251 values[name] = value 1acdefbg
1252 continue 1acdefbg
1253 # Priority 2: Value was set in @subapp.callback()
1254 try: 1acdefbg
1255 callback_value = getattr( 1acdefbg
1256 typer_info.typer_instance.registered_callback, # type: ignore
1257 name,
1258 )
1259 if not isinstance(callback_value, DefaultPlaceholder): 1acdefbg
1260 values[name] = callback_value 1acdefbg
1261 continue 1acdefbg
1262 except AttributeError: 1acdefbg
1263 pass 1acdefbg
1264 # Priority 3: Value set in subapp = typer.Typer()
1265 try: 1acdefbg
1266 instance_value = getattr( 1acdefbg
1267 typer_info.typer_instance.info, # type: ignore
1268 name,
1269 )
1270 if not isinstance(instance_value, DefaultPlaceholder): 1acdefbg
1271 values[name] = instance_value 1acdefbg
1272 continue 1acdefbg
1273 except AttributeError: 1acdefbg
1274 pass 1acdefbg
1275 # Value not set, use the default
1276 values[name] = value.value 1acdefbg
1277 values["help"] = solve_typer_info_help(typer_info) 1acdefbg
1278 return TyperInfo(**values) 1acdefbg
1281def get_group_from_info( 1acdefbg
1282 group_info: TyperInfo,
1283 *,
1284 pretty_exceptions_short: bool,
1285 suggest_commands: bool,
1286 rich_markup_mode: MarkupMode,
1287) -> TyperGroup:
1288 assert group_info.typer_instance, ( 1acdefbg
1289 "A Typer instance is needed to generate a Click Group"
1290 )
1291 commands: dict[str, click.Command] = {} 1acdefbg
1292 for command_info in group_info.typer_instance.registered_commands: 1acdefbg
1293 command = get_command_from_info( 1acdefbg
1294 command_info=command_info,
1295 pretty_exceptions_short=pretty_exceptions_short,
1296 rich_markup_mode=rich_markup_mode,
1297 )
1298 if command.name: 1acdefbg
1299 commands[command.name] = command 1acdefbg
1300 for sub_group_info in group_info.typer_instance.registered_groups: 1acdefbg
1301 sub_group = get_group_from_info( 1acdefbg
1302 sub_group_info,
1303 pretty_exceptions_short=pretty_exceptions_short,
1304 rich_markup_mode=rich_markup_mode,
1305 suggest_commands=suggest_commands,
1306 )
1307 if sub_group.name: 1acdefbg
1308 commands[sub_group.name] = sub_group 1acdefbg
1309 else:
1310 if sub_group.callback: 1acdefbg
1311 import warnings 1acdefbg
1313 warnings.warn( 1acdefbg
1314 "The 'callback' parameter is not supported by Typer when using `add_typer` without a name",
1315 stacklevel=5,
1316 )
1317 for sub_command_name, sub_command in sub_group.commands.items(): 1acdefbg
1318 commands[sub_command_name] = sub_command 1acdefbg
1319 solved_info = solve_typer_info_defaults(group_info) 1acdefbg
1320 ( 1acdefbg
1321 params,
1322 convertors,
1323 context_param_name,
1324 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback)
1325 cls = solved_info.cls or TyperGroup 1acdefbg
1326 assert issubclass(cls, TyperGroup), f"{cls} should be a subclass of {TyperGroup}" 1acdefbg
1327 group = cls( 1acdefbg
1328 name=solved_info.name or "",
1329 commands=commands,
1330 invoke_without_command=solved_info.invoke_without_command,
1331 no_args_is_help=solved_info.no_args_is_help,
1332 subcommand_metavar=solved_info.subcommand_metavar,
1333 chain=solved_info.chain,
1334 result_callback=solved_info.result_callback,
1335 context_settings=solved_info.context_settings,
1336 callback=get_callback(
1337 callback=solved_info.callback,
1338 params=params,
1339 convertors=convertors,
1340 context_param_name=context_param_name,
1341 pretty_exceptions_short=pretty_exceptions_short,
1342 ),
1343 params=params,
1344 help=solved_info.help,
1345 epilog=solved_info.epilog,
1346 short_help=solved_info.short_help,
1347 options_metavar=solved_info.options_metavar,
1348 add_help_option=solved_info.add_help_option,
1349 hidden=solved_info.hidden,
1350 deprecated=solved_info.deprecated,
1351 rich_markup_mode=rich_markup_mode,
1352 # Rich settings
1353 rich_help_panel=solved_info.rich_help_panel,
1354 suggest_commands=suggest_commands,
1355 )
1356 return group 1acdefbg
1359def get_command_name(name: str) -> str: 1acdefbg
1360 return name.lower().replace("_", "-") 1acdefbg
1363def get_params_convertors_ctx_param_name_from_function( 1acdefbg
1364 callback: Callable[..., Any] | None,
1365) -> tuple[list[click.Argument | click.Option], dict[str, Any], str | None]:
1366 params = [] 1acdefbg
1367 convertors = {} 1acdefbg
1368 context_param_name = None 1acdefbg
1369 if callback: 1acdefbg
1370 parameters = get_params_from_function(callback) 1acdefbg
1371 for param_name, param in parameters.items(): 1acdefbg
1372 if lenient_issubclass(param.annotation, click.Context): 1acdefbg
1373 context_param_name = param_name 1acdefbg
1374 continue 1acdefbg
1375 click_param, convertor = get_click_param(param) 1acdefbg
1376 if convertor: 1acdefbg
1377 convertors[param_name] = convertor 1acdefbg
1378 params.append(click_param) 1acdefbg
1379 return params, convertors, context_param_name 1acdefbg
1382def get_command_from_info( 1acdefbg
1383 command_info: CommandInfo,
1384 *,
1385 pretty_exceptions_short: bool,
1386 rich_markup_mode: MarkupMode,
1387) -> click.Command:
1388 assert command_info.callback, "A command must have a callback function" 1acdefbg
1389 name = command_info.name or get_command_name(command_info.callback.__name__) # ty: ignore 1acdefbg
1390 use_help = command_info.help 1acdefbg
1391 if use_help is None: 1acdefbg
1392 use_help = inspect.getdoc(command_info.callback) 1acdefbg
1393 else:
1394 use_help = inspect.cleandoc(use_help) 1acdefbg
1395 ( 1acdefbg
1396 params,
1397 convertors,
1398 context_param_name,
1399 ) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
1400 cls = command_info.cls or TyperCommand 1acdefbg
1401 command = cls( 1acdefbg
1402 name=name,
1403 context_settings=command_info.context_settings,
1404 callback=get_callback(
1405 callback=command_info.callback,
1406 params=params,
1407 convertors=convertors,
1408 context_param_name=context_param_name,
1409 pretty_exceptions_short=pretty_exceptions_short,
1410 ),
1411 params=params, # type: ignore
1412 help=use_help,
1413 epilog=command_info.epilog,
1414 short_help=command_info.short_help,
1415 options_metavar=command_info.options_metavar,
1416 add_help_option=command_info.add_help_option,
1417 no_args_is_help=command_info.no_args_is_help,
1418 hidden=command_info.hidden,
1419 deprecated=command_info.deprecated,
1420 rich_markup_mode=rich_markup_mode,
1421 # Rich settings
1422 rich_help_panel=command_info.rich_help_panel,
1423 )
1424 return command 1acdefbg
1427def determine_type_convertor(type_: Any) -> Callable[[Any], Any] | None: 1acdefbg
1428 convertor: Callable[[Any], Any] | None = None 1acdefbg
1429 if lenient_issubclass(type_, Path): 1acdefbg
1430 convertor = param_path_convertor 1acdefbg
1431 if lenient_issubclass(type_, Enum): 1acdefbg
1432 convertor = generate_enum_convertor(type_) 1acdefbg
1433 return convertor 1acdefbg
1436def param_path_convertor(value: str | None = None) -> Path | None: 1acdefbg
1437 if value is not None: 1acdefbg
1438 # allow returning any subclass of Path created by an annotated parser without converting
1439 # it back to a Path
1440 return value if isinstance(value, Path) else Path(value) 1acdefbg
1441 return None 1acdefbg
1444def generate_enum_convertor(enum: type[Enum]) -> Callable[[Any], Any]: 1acdefbg
1445 val_map = {str(val.value): val for val in enum} 1acdefbg
1447 def convertor(value: Any) -> Any: 1acdefbg
1448 if value is not None: 1acdefbg
1449 val = str(value) 1acdefbg
1450 if val in val_map: 1acdefbg
1451 key = val_map[val] 1acdefbg
1452 return enum(key) 1acdefbg
1454 return convertor 1acdefbg
1457def generate_list_convertor( 1acdefbg
1458 convertor: Callable[[Any], Any] | None, default_value: Any | None
1459) -> Callable[[Sequence[Any] | None], list[Any] | None]:
1460 def internal_convertor(value: Sequence[Any] | None) -> list[Any] | None: 1acdefbg
1461 if (value is None) or (default_value is None and len(value) == 0): 1acdefbg
1462 return None 1acdefbg
1463 return [convertor(v) if convertor else v for v in value] 1acdefbg
1465 return internal_convertor 1acdefbg
1468def generate_tuple_convertor( 1acdefbg
1469 types: Sequence[Any],
1470) -> Callable[[tuple[Any, ...] | None], tuple[Any, ...] | None]:
1471 convertors = [determine_type_convertor(type_) for type_ in types] 1acdefbg
1473 def internal_convertor( 1acdefbg
1474 param_args: tuple[Any, ...] | None,
1475 ) -> tuple[Any, ...] | None:
1476 if param_args is None: 1acdefbg
1477 return None 1acdefbg
1478 return tuple( 1acdefbg
1479 convertor(arg) if convertor else arg
1480 for (convertor, arg) in zip(convertors, param_args, strict=False)
1481 )
1483 return internal_convertor 1acdefbg
1486def get_callback( 1acdefbg
1487 *,
1488 callback: Callable[..., Any] | None = None,
1489 params: Sequence[click.Parameter] = [],
1490 convertors: dict[str, Callable[[str], Any]] | None = None,
1491 context_param_name: str | None = None,
1492 pretty_exceptions_short: bool,
1493) -> Callable[..., Any] | None:
1494 use_convertors = convertors or {} 1acdefbg
1495 if not callback: 1acdefbg
1496 return None 1acdefbg
1497 parameters = get_params_from_function(callback) 1acdefbg
1498 use_params: dict[str, Any] = {} 1acdefbg
1499 for param_name in parameters: 1acdefbg
1500 use_params[param_name] = None 1acdefbg
1501 for param in params: 1acdefbg
1502 if param.name: 1acdefbg
1503 use_params[param.name] = param.default 1acdefbg
1505 def wrapper(**kwargs: Any) -> Any: 1acdefbg
1506 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1acdefbg
1507 for k, v in kwargs.items(): 1acdefbg
1508 if k in use_convertors: 1acdefbg
1509 use_params[k] = use_convertors[k](v) 1acdefbg
1510 else:
1511 use_params[k] = v 1acdefbg
1512 if context_param_name: 1acdefbg
1513 use_params[context_param_name] = click.get_current_context() 1acdefbg
1514 return callback(**use_params) 1acdefbg
1516 update_wrapper(wrapper, callback) 1acdefbg
1517 return wrapper 1acdefbg
1520def get_click_type( 1acdefbg
1521 *, annotation: Any, parameter_info: ParameterInfo
1522) -> click.ParamType:
1523 if parameter_info.click_type is not None: 1acdefbg
1524 return parameter_info.click_type 1acdefbg
1526 elif parameter_info.parser is not None: 1acdefbg
1527 return click.types.FuncParamType(parameter_info.parser) 1acdefbg
1529 elif annotation is str: 1acdefbg
1530 return click.STRING 1acdefbg
1531 elif annotation is int: 1acdefbg
1532 if parameter_info.min is not None or parameter_info.max is not None: 1acdefbg
1533 min_ = None 1acdefbg
1534 max_ = None 1acdefbg
1535 if parameter_info.min is not None: 1acdefbg
1536 min_ = int(parameter_info.min) 1acdefbg
1537 if parameter_info.max is not None: 1acdefbg
1538 max_ = int(parameter_info.max) 1acdefbg
1539 return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1acdefbg
1540 else:
1541 return click.INT 1acdefbg
1542 elif annotation is float: 1acdefbg
1543 if parameter_info.min is not None or parameter_info.max is not None: 1acdefbg
1544 return click.FloatRange( 1acdefbg
1545 min=parameter_info.min,
1546 max=parameter_info.max,
1547 clamp=parameter_info.clamp,
1548 )
1549 else:
1550 return click.FLOAT 1acdefbg
1551 elif annotation is bool: 1acdefbg
1552 return click.BOOL 1acdefbg
1553 elif annotation == UUID: 1acdefbg
1554 return click.UUID 1acdefbg
1555 elif annotation == datetime: 1acdefbg
1556 return click.DateTime(formats=parameter_info.formats) 1acdefbg
1557 elif (
1558 annotation == Path
1559 or parameter_info.allow_dash
1560 or parameter_info.path_type
1561 or parameter_info.resolve_path
1562 ):
1563 return TyperPath( 1acdefbg
1564 exists=parameter_info.exists,
1565 file_okay=parameter_info.file_okay,
1566 dir_okay=parameter_info.dir_okay,
1567 writable=parameter_info.writable,
1568 readable=parameter_info.readable,
1569 resolve_path=parameter_info.resolve_path,
1570 allow_dash=parameter_info.allow_dash,
1571 path_type=parameter_info.path_type,
1572 )
1573 elif lenient_issubclass(annotation, FileTextWrite): 1acdefbg
1574 return click.File( 1acdefbg
1575 mode=parameter_info.mode or "w",
1576 encoding=parameter_info.encoding,
1577 errors=parameter_info.errors,
1578 lazy=parameter_info.lazy,
1579 atomic=parameter_info.atomic,
1580 )
1581 elif lenient_issubclass(annotation, FileText): 1acdefbg
1582 return click.File( 1acdefbg
1583 mode=parameter_info.mode or "r",
1584 encoding=parameter_info.encoding,
1585 errors=parameter_info.errors,
1586 lazy=parameter_info.lazy,
1587 atomic=parameter_info.atomic,
1588 )
1589 elif lenient_issubclass(annotation, FileBinaryRead): 1acdefbg
1590 return click.File( 1acdefbg
1591 mode=parameter_info.mode or "rb",
1592 encoding=parameter_info.encoding,
1593 errors=parameter_info.errors,
1594 lazy=parameter_info.lazy,
1595 atomic=parameter_info.atomic,
1596 )
1597 elif lenient_issubclass(annotation, FileBinaryWrite): 1acdefbg
1598 return click.File( 1acdefbg
1599 mode=parameter_info.mode or "wb",
1600 encoding=parameter_info.encoding,
1601 errors=parameter_info.errors,
1602 lazy=parameter_info.lazy,
1603 atomic=parameter_info.atomic,
1604 )
1605 elif lenient_issubclass(annotation, Enum): 1acdefbg
1606 # The custom TyperChoice is only needed for Click < 8.2.0, to parse the
1607 # command line values matching them to the enum values. Click 8.2.0 added
1608 # support for enum values but reading enum names.
1609 # Passing here the list of enum values (instead of just the enum) accounts for
1610 # Click < 8.2.0.
1611 return TyperChoice( 1acdefbg
1612 [item.value for item in annotation],
1613 case_sensitive=parameter_info.case_sensitive,
1614 )
1615 elif is_literal_type(annotation): 1acdefbg
1616 return click.Choice( 1acdefbg
1617 literal_values(annotation),
1618 case_sensitive=parameter_info.case_sensitive,
1619 )
1620 raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover
1623def lenient_issubclass(cls: Any, class_or_tuple: AnyType | tuple[AnyType, ...]) -> bool: 1acdefbg
1624 return isinstance(cls, type) and issubclass(cls, class_or_tuple) 1acdefbg
1627def get_click_param( 1acdefbg
1628 param: ParamMeta,
1629) -> tuple[click.Argument | click.Option, Any]:
1630 # First, find out what will be:
1631 # * ParamInfo (ArgumentInfo or OptionInfo)
1632 # * default_value
1633 # * required
1634 default_value = None 1acdefbg
1635 required = False 1acdefbg
1636 if isinstance(param.default, ParameterInfo): 1acdefbg
1637 parameter_info = param.default 1acdefbg
1638 if parameter_info.default == Required: 1acdefbg
1639 required = True 1acdefbg
1640 else:
1641 default_value = parameter_info.default 1acdefbg
1642 elif param.default == Required or param.default is param.empty: 1acdefbg
1643 required = True 1acdefbg
1644 parameter_info = ArgumentInfo() 1acdefbg
1645 else:
1646 default_value = param.default 1acdefbg
1647 parameter_info = OptionInfo() 1acdefbg
1648 annotation: Any
1649 if param.annotation is not param.empty: 1acdefbg
1650 annotation = param.annotation 1acdefbg
1651 else:
1652 annotation = str 1acdefbg
1653 main_type = annotation 1acdefbg
1654 is_list = False 1acdefbg
1655 is_tuple = False 1acdefbg
1656 parameter_type: Any = None 1acdefbg
1657 is_flag = None 1acdefbg
1658 origin = get_origin(main_type) 1acdefbg
1660 if origin is not None: 1acdefbg
1661 # Handle SomeType | None and Optional[SomeType]
1662 if is_union(origin): 1acdefbg
1663 types = [] 1acdefbg
1664 for type_ in get_args(main_type): 1acdefbg
1665 if type_ is NoneType: 1acdefbg
1666 continue 1acdefbg
1667 types.append(type_) 1acdefbg
1668 assert len(types) == 1, "Typer Currently doesn't support Union types" 1acdefbg
1669 main_type = types[0] 1acdefbg
1670 origin = get_origin(main_type) 1acdefbg
1671 # Handle Tuples and Lists
1672 if lenient_issubclass(origin, list): 1acdefbg
1673 main_type = get_args(main_type)[0] 1acdefbg
1674 assert not get_origin(main_type), ( 1acdefbg
1675 "List types with complex sub-types are not currently supported"
1676 )
1677 is_list = True 1acdefbg
1678 elif lenient_issubclass(origin, tuple): 1acdefbg
1679 types = [] 1acdefbg
1680 for type_ in get_args(main_type): 1acdefbg
1681 assert not get_origin(type_), ( 1acdefbg
1682 "Tuple types with complex sub-types are not currently supported"
1683 )
1684 types.append( 1acdefbg
1685 get_click_type(annotation=type_, parameter_info=parameter_info)
1686 )
1687 parameter_type = tuple(types) 1acdefbg
1688 is_tuple = True 1acdefbg
1689 if parameter_type is None: 1acdefbg
1690 parameter_type = get_click_type( 1acdefbg
1691 annotation=main_type, parameter_info=parameter_info
1692 )
1693 convertor = determine_type_convertor(main_type) 1acdefbg
1694 if is_list: 1acdefbg
1695 convertor = generate_list_convertor( 1acdefbg
1696 convertor=convertor, default_value=default_value
1697 )
1698 if is_tuple: 1acdefbg
1699 convertor = generate_tuple_convertor(get_args(main_type)) 1acdefbg
1700 if isinstance(parameter_info, OptionInfo): 1acdefbg
1701 if main_type is bool: 1acdefbg
1702 is_flag = True 1acdefbg
1703 # Click doesn't accept a flag of type bool, only None, and then it sets it
1704 # to bool internally
1705 parameter_type = None 1acdefbg
1706 default_option_name = get_command_name(param.name) 1acdefbg
1707 if is_flag: 1acdefbg
1708 default_option_declaration = ( 1acdefbg
1709 f"--{default_option_name}/--no-{default_option_name}"
1710 )
1711 else:
1712 default_option_declaration = f"--{default_option_name}" 1acdefbg
1713 param_decls = [param.name] 1acdefbg
1714 if parameter_info.param_decls: 1acdefbg
1715 param_decls.extend(parameter_info.param_decls) 1acdefbg
1716 else:
1717 param_decls.append(default_option_declaration) 1acdefbg
1718 return ( 1acdefbg
1719 TyperOption(
1720 # Option
1721 param_decls=param_decls,
1722 show_default=parameter_info.show_default,
1723 prompt=parameter_info.prompt,
1724 confirmation_prompt=parameter_info.confirmation_prompt,
1725 prompt_required=parameter_info.prompt_required,
1726 hide_input=parameter_info.hide_input,
1727 is_flag=is_flag,
1728 multiple=is_list,
1729 count=parameter_info.count,
1730 allow_from_autoenv=parameter_info.allow_from_autoenv,
1731 type=parameter_type,
1732 help=parameter_info.help,
1733 hidden=parameter_info.hidden,
1734 show_choices=parameter_info.show_choices,
1735 show_envvar=parameter_info.show_envvar,
1736 # Parameter
1737 required=required,
1738 default=default_value,
1739 callback=get_param_callback(
1740 callback=parameter_info.callback, convertor=convertor
1741 ),
1742 metavar=parameter_info.metavar,
1743 expose_value=parameter_info.expose_value,
1744 is_eager=parameter_info.is_eager,
1745 envvar=parameter_info.envvar,
1746 shell_complete=parameter_info.shell_complete,
1747 autocompletion=get_param_completion(parameter_info.autocompletion),
1748 # Rich settings
1749 rich_help_panel=parameter_info.rich_help_panel,
1750 ),
1751 convertor,
1752 )
1753 elif isinstance(parameter_info, ArgumentInfo): 1acdefbg
1754 param_decls = [param.name] 1acdefbg
1755 nargs = None 1acdefbg
1756 if is_list: 1acdefbg
1757 nargs = -1 1acdefbg
1758 return ( 1acdefbg
1759 TyperArgument(
1760 # Argument
1761 param_decls=param_decls,
1762 type=parameter_type,
1763 required=required,
1764 nargs=nargs,
1765 # TyperArgument
1766 show_default=parameter_info.show_default,
1767 show_choices=parameter_info.show_choices,
1768 show_envvar=parameter_info.show_envvar,
1769 help=parameter_info.help,
1770 hidden=parameter_info.hidden,
1771 # Parameter
1772 default=default_value,
1773 callback=get_param_callback(
1774 callback=parameter_info.callback, convertor=convertor
1775 ),
1776 metavar=parameter_info.metavar,
1777 expose_value=parameter_info.expose_value,
1778 is_eager=parameter_info.is_eager,
1779 envvar=parameter_info.envvar,
1780 shell_complete=parameter_info.shell_complete,
1781 autocompletion=get_param_completion(parameter_info.autocompletion),
1782 # Rich settings
1783 rich_help_panel=parameter_info.rich_help_panel,
1784 ),
1785 convertor,
1786 )
1787 raise AssertionError("A click.Parameter should be returned") # pragma: no cover
1790def get_param_callback( 1acdefbg
1791 *,
1792 callback: Callable[..., Any] | None = None,
1793 convertor: Callable[..., Any] | None = None,
1794) -> Callable[..., Any] | None:
1795 if not callback: 1acdefbg
1796 return None 1acdefbg
1797 parameters = get_params_from_function(callback) 1acdefbg
1798 ctx_name = None 1acdefbg
1799 click_param_name = None 1acdefbg
1800 value_name = None 1acdefbg
1801 untyped_names: list[str] = [] 1acdefbg
1802 for param_name, param_sig in parameters.items(): 1acdefbg
1803 if lenient_issubclass(param_sig.annotation, click.Context): 1acdefbg
1804 ctx_name = param_name 1acdefbg
1805 elif lenient_issubclass(param_sig.annotation, click.Parameter): 1acdefbg
1806 click_param_name = param_name 1acdefbg
1807 else:
1808 untyped_names.append(param_name) 1acdefbg
1809 # Extract value param name first
1810 if untyped_names: 1acdefbg
1811 value_name = untyped_names.pop() 1acdefbg
1812 # If context and Click param were not typed (old/Click callback style) extract them
1813 if untyped_names: 1acdefbg
1814 if ctx_name is None: 1acdefbg
1815 ctx_name = untyped_names.pop(0) 1acdefbg
1816 if click_param_name is None: 1acdefbg
1817 if untyped_names: 1acdefbg
1818 click_param_name = untyped_names.pop(0) 1acdefbg
1819 if untyped_names: 1acdefbg
1820 raise click.ClickException( 1acdefbg
1821 "Too many CLI parameter callback function parameters"
1822 )
1824 def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: 1acdefbg
1825 use_params: dict[str, Any] = {} 1acdefbg
1826 if ctx_name: 1acdefbg
1827 use_params[ctx_name] = ctx 1acdefbg
1828 if click_param_name: 1acdefbg
1829 use_params[click_param_name] = param 1acdefbg
1830 if value_name: 1acdefbg
1831 if convertor: 1acdefbg
1832 use_value = convertor(value) 1acdefbg
1833 else:
1834 use_value = value 1acdefbg
1835 use_params[value_name] = use_value 1acdefbg
1836 return callback(**use_params) 1acdefbg
1838 update_wrapper(wrapper, callback) 1acdefbg
1839 return wrapper 1acdefbg
1842def get_param_completion( 1acdefbg
1843 callback: Callable[..., Any] | None = None,
1844) -> Callable[..., Any] | None:
1845 if not callback: 1acdefbg
1846 return None 1acdefbg
1847 parameters = get_params_from_function(callback) 1acdefbg
1848 ctx_name = None 1acdefbg
1849 args_name = None 1acdefbg
1850 incomplete_name = None 1acdefbg
1851 unassigned_params = list(parameters.values()) 1acdefbg
1852 for param_sig in unassigned_params[:]: 1acdefbg
1853 origin = get_origin(param_sig.annotation) 1acdefbg
1854 if lenient_issubclass(param_sig.annotation, click.Context): 1acdefbg
1855 ctx_name = param_sig.name 1acdefbg
1856 unassigned_params.remove(param_sig) 1acdefbg
1857 elif lenient_issubclass(origin, list): 1acdefbg
1858 args_name = param_sig.name 1acdefbg
1859 unassigned_params.remove(param_sig) 1acdefbg
1860 elif lenient_issubclass(param_sig.annotation, str): 1acdefbg
1861 incomplete_name = param_sig.name 1acdefbg
1862 unassigned_params.remove(param_sig) 1acdefbg
1863 # If there are still unassigned parameters (not typed), extract by name
1864 for param_sig in unassigned_params[:]: 1acdefbg
1865 if ctx_name is None and param_sig.name == "ctx": 1acdefbg
1866 ctx_name = param_sig.name 1acdefbg
1867 unassigned_params.remove(param_sig) 1acdefbg
1868 elif args_name is None and param_sig.name == "args": 1acdefbg
1869 args_name = param_sig.name 1acdefbg
1870 unassigned_params.remove(param_sig) 1acdefbg
1871 elif incomplete_name is None and param_sig.name == "incomplete": 1acdefbg
1872 incomplete_name = param_sig.name 1acdefbg
1873 unassigned_params.remove(param_sig) 1acdefbg
1874 # Extract value param name first
1875 if unassigned_params: 1acdefbg
1876 show_params = " ".join([param.name for param in unassigned_params]) 1acdefbg
1877 raise click.ClickException( 1acdefbg
1878 f"Invalid autocompletion callback parameters: {show_params}"
1879 )
1881 def wrapper(ctx: click.Context, args: list[str], incomplete: str | None) -> Any: 1acdefbg
1882 use_params: dict[str, Any] = {} 1acdefbg
1883 if ctx_name: 1acdefbg
1884 use_params[ctx_name] = ctx 1acdefbg
1885 if args_name: 1acdefbg
1886 use_params[args_name] = args 1acdefbg
1887 if incomplete_name: 1acdefbg
1888 use_params[incomplete_name] = incomplete 1acdefbg
1889 return callback(**use_params) 1acdefbg
1891 update_wrapper(wrapper, callback) 1acdefbg
1892 return wrapper 1acdefbg
1895def run( 1acdefbg
1896 function: Annotated[
1897 Callable[..., Any],
1898 Doc(
1899 """
1900 The function that should power this CLI application.
1901 """
1902 ),
1903 ],
1904) -> None:
1905 """
1906 This function converts a given function to a CLI application with `Typer()` and executes it.
1908 ## Example
1910 ```python
1911 import typer
1913 def main(name: str):
1914 print(f"Hello {name}")
1916 if __name__ == "__main__":
1917 typer.run(main)
1918 ```
1919 """
1920 app = Typer(add_completion=False) 1acdefbg
1921 app.command()(function) 1acdefbg
1922 app() 1acdefbg
1925def _is_macos() -> bool: 1acdefbg
1926 return platform.system() == "Darwin" 1acdefbg
1929def _is_linux_or_bsd() -> bool: 1acdefbg
1930 if platform.system() == "Linux": 1acdefbg
1931 return True 1acdefbg
1933 return "BSD" in platform.system() 1acdefbg
1936def launch( 1acdefbg
1937 url: Annotated[
1938 str,
1939 Doc(
1940 """
1941 URL or filename of the thing to launch.
1942 """
1943 ),
1944 ],
1945 wait: Annotated[
1946 bool,
1947 Doc(
1948 """
1949 Wait for the program to exit before returning. This only works if the launched program blocks.
1950 In particular, `xdg-open` on Linux does not block.
1951 """
1952 ),
1953 ] = False,
1954 locate: Annotated[
1955 bool,
1956 Doc(
1957 """
1958 If this is set to `True`, then instead of launching the application associated with the URL, it will attempt to
1959 launch a file manager with the file located. This might have weird effects if the URL does not point to the filesystem.
1960 """
1961 ),
1962 ] = False,
1963) -> int:
1964 """
1965 This function launches the given URL (or filename) in the default
1966 viewer application for this file type. If this is an executable, it
1967 might launch the executable in a new session. The return value is
1968 the exit code of the launched application. Usually, `0` indicates
1969 success.
1971 This function handles url in different operating systems separately:
1972 - On macOS (Darwin), it uses the `open` command.
1973 - On Linux and BSD, it uses `xdg-open` if available.
1974 - On Windows (and other OSes), it uses the standard webbrowser module.
1976 The function avoids, when possible, using the webbrowser module on Linux and macOS
1977 to prevent spammy terminal messages from some browsers (e.g., Chrome).
1979 ## Examples
1980 ```python
1981 import typer
1983 typer.launch("https://typer.tiangolo.com/")
1984 ```
1986 ```python
1987 import typer
1989 typer.launch("/my/downloaded/file", locate=True)
1990 ```
1991 """
1993 if url.startswith("http://") or url.startswith("https://"): 1acdefbg
1994 if _is_macos(): 1acdefbg
1995 return subprocess.Popen( 1acdefbg
1996 ["open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
1997 ).wait()
1999 has_xdg_open = _is_linux_or_bsd() and shutil.which("xdg-open") is not None 1acdefbg
2001 if has_xdg_open: 1acdefbg
2002 return subprocess.Popen( 1acdefbg
2003 ["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
2004 ).wait()
2006 import webbrowser 1acdefbg
2008 webbrowser.open(url) 1acdefbg
2010 return 0 1acdefbg
2012 else:
2013 return click.launch(url) 1acdefbg