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