Coverage for typer/main.py: 100%
491 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-03-10 00:15 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-03-10 00:15 +0000
1import inspect 1iceafgbdh
2import os 1iceafgbdh
3import platform 1iceafgbdh
4import shutil 1iceafgbdh
5import subprocess 1iceafgbdh
6import sys 1iceafgbdh
7import traceback 1iceafgbdh
8from datetime import datetime 1iceafgbdh
9from enum import Enum 1iceafgbdh
10from functools import update_wrapper 1iceafgbdh
11from pathlib import Path 1iceafgbdh
12from traceback import FrameSummary, StackSummary 1iceafgbdh
13from types import TracebackType 1iceafgbdh
14from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union 1iceafgbdh
15from uuid import UUID 1iceafgbdh
17import click 1iceafgbdh
19from ._typing import get_args, get_origin, is_union 1iceafgbdh
20from .completion import get_completion_inspect_parameters 1iceafgbdh
21from .core import ( 1iceafgbdh
22 DEFAULT_MARKUP_MODE,
23 MarkupMode,
24 TyperArgument,
25 TyperCommand,
26 TyperGroup,
27 TyperOption,
28)
29from .models import ( 1iceafgbdh
30 AnyType,
31 ArgumentInfo,
32 CommandFunctionType,
33 CommandInfo,
34 Default,
35 DefaultPlaceholder,
36 DeveloperExceptionConfig,
37 FileBinaryRead,
38 FileBinaryWrite,
39 FileText,
40 FileTextWrite,
41 NoneType,
42 OptionInfo,
43 ParameterInfo,
44 ParamMeta,
45 Required,
46 TyperInfo,
47)
48from .utils import get_params_from_function 1iceafgbdh
50try: 1iceafgbdh
51 import rich 1iceafgbdh
52 from rich.traceback import Traceback 1iceafgbdh
54 from . import rich_utils 1iceafgbdh
56 console_stderr = rich_utils._get_rich_console(stderr=True) 1iceafgbdh
58except ImportError: # pragma: no cover
59 rich = None # type: ignore
61_original_except_hook = sys.excepthook 1iceafgbdh
62_typer_developer_exception_attr_name = "__typer_developer_exception__" 1iceafgbdh
65def except_hook( 1ceafgbdh
66 exc_type: Type[BaseException], exc_value: BaseException, tb: Optional[TracebackType]
67) -> None:
68 exception_config: Union[DeveloperExceptionConfig, None] = getattr( 1iceafgbdh
69 exc_value, _typer_developer_exception_attr_name, None
70 )
71 standard_traceback = os.getenv("_TYPER_STANDARD_TRACEBACK") 1iceafgbdh
72 if ( 1cabd
73 standard_traceback
74 or not exception_config
75 or not exception_config.pretty_exceptions_enable
76 ):
77 _original_except_hook(exc_type, exc_value, tb) 1iceafgbdh
78 return 1iceafgbdh
79 typer_path = os.path.dirname(__file__) 1iceafgbdh
80 click_path = os.path.dirname(click.__file__) 1iceafgbdh
81 supress_internal_dir_names = [typer_path, click_path] 1iceafgbdh
82 exc = exc_value 1iceafgbdh
83 if rich: 1iceafgbdh
84 from .rich_utils import MAX_WIDTH 1iceafgbdh
86 rich_tb = Traceback.from_exception( 1iceafgbdh
87 type(exc),
88 exc,
89 exc.__traceback__,
90 show_locals=exception_config.pretty_exceptions_show_locals,
91 suppress=supress_internal_dir_names,
92 width=MAX_WIDTH,
93 )
94 console_stderr.print(rich_tb) 1iceafgbdh
95 return 1iceafgbdh
96 tb_exc = traceback.TracebackException.from_exception(exc) 1iceafgbdh
97 stack: List[FrameSummary] = [] 1iceafgbdh
98 for frame in tb_exc.stack: 1iceafgbdh
99 if any(frame.filename.startswith(path) for path in supress_internal_dir_names): 1iceafgbdh
100 if not exception_config.pretty_exceptions_short: 1iceafgbdh
101 # Hide the line for internal libraries, Typer and Click
102 stack.append( 1iceafgbdh
103 traceback.FrameSummary(
104 filename=frame.filename,
105 lineno=frame.lineno,
106 name=frame.name,
107 line="",
108 )
109 )
110 else:
111 stack.append(frame) 1iceafgbdh
112 # Type ignore ref: https://github.com/python/typeshed/pull/8244
113 final_stack_summary = StackSummary.from_list(stack) 1iceafgbdh
114 tb_exc.stack = final_stack_summary 1iceafgbdh
115 for line in tb_exc.format(): 1iceafgbdh
116 print(line, file=sys.stderr) 1iceafgbdh
117 return 1iceafgbdh
120def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]: 1iceafgbdh
121 install_param, show_param = get_completion_inspect_parameters() 1iceafgbdh
122 click_install_param, _ = get_click_param(install_param) 1iceafgbdh
123 click_show_param, _ = get_click_param(show_param) 1iceafgbdh
124 return click_install_param, click_show_param 1iceafgbdh
127class Typer: 1iceafgbdh
128 def __init__( 1ceafgbdh
129 self,
130 *,
131 name: Optional[str] = Default(None),
132 cls: Optional[Type[TyperGroup]] = Default(None),
133 invoke_without_command: bool = Default(False),
134 no_args_is_help: bool = Default(False),
135 subcommand_metavar: Optional[str] = Default(None),
136 chain: bool = Default(False),
137 result_callback: Optional[Callable[..., Any]] = Default(None),
138 # Command
139 context_settings: Optional[Dict[Any, Any]] = Default(None),
140 callback: Optional[Callable[..., Any]] = Default(None),
141 help: Optional[str] = Default(None),
142 epilog: Optional[str] = Default(None),
143 short_help: Optional[str] = Default(None),
144 options_metavar: str = Default("[OPTIONS]"),
145 add_help_option: bool = Default(True),
146 hidden: bool = Default(False),
147 deprecated: bool = Default(False),
148 add_completion: bool = True,
149 # Rich settings
150 rich_markup_mode: MarkupMode = Default(DEFAULT_MARKUP_MODE),
151 rich_help_panel: Union[str, None] = Default(None),
152 pretty_exceptions_enable: bool = True,
153 pretty_exceptions_show_locals: bool = True,
154 pretty_exceptions_short: bool = True,
155 ):
156 self._add_completion = add_completion 1iceafgbdh
157 self.rich_markup_mode: MarkupMode = rich_markup_mode 1iceafgbdh
158 self.rich_help_panel = rich_help_panel 1iceafgbdh
159 self.pretty_exceptions_enable = pretty_exceptions_enable 1iceafgbdh
160 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1iceafgbdh
161 self.pretty_exceptions_short = pretty_exceptions_short 1iceafgbdh
162 self.info = TyperInfo( 1iceafgbdh
163 name=name,
164 cls=cls,
165 invoke_without_command=invoke_without_command,
166 no_args_is_help=no_args_is_help,
167 subcommand_metavar=subcommand_metavar,
168 chain=chain,
169 result_callback=result_callback,
170 context_settings=context_settings,
171 callback=callback,
172 help=help,
173 epilog=epilog,
174 short_help=short_help,
175 options_metavar=options_metavar,
176 add_help_option=add_help_option,
177 hidden=hidden,
178 deprecated=deprecated,
179 )
180 self.registered_groups: List[TyperInfo] = [] 1iceafgbdh
181 self.registered_commands: List[CommandInfo] = [] 1iceafgbdh
182 self.registered_callback: Optional[TyperInfo] = None 1iceafgbdh
184 def callback( 1ceafgbdh
185 self,
186 *,
187 cls: Optional[Type[TyperGroup]] = Default(None),
188 invoke_without_command: bool = Default(False),
189 no_args_is_help: bool = Default(False),
190 subcommand_metavar: Optional[str] = Default(None),
191 chain: bool = Default(False),
192 result_callback: Optional[Callable[..., Any]] = Default(None),
193 # Command
194 context_settings: Optional[Dict[Any, Any]] = Default(None),
195 help: Optional[str] = Default(None),
196 epilog: Optional[str] = Default(None),
197 short_help: Optional[str] = Default(None),
198 options_metavar: str = Default("[OPTIONS]"),
199 add_help_option: bool = Default(True),
200 hidden: bool = Default(False),
201 deprecated: bool = Default(False),
202 # Rich settings
203 rich_help_panel: Union[str, None] = Default(None),
204 ) -> Callable[[CommandFunctionType], CommandFunctionType]:
205 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1iceafgbdh
206 self.registered_callback = TyperInfo( 1iceafgbdh
207 cls=cls,
208 invoke_without_command=invoke_without_command,
209 no_args_is_help=no_args_is_help,
210 subcommand_metavar=subcommand_metavar,
211 chain=chain,
212 result_callback=result_callback,
213 context_settings=context_settings,
214 callback=f,
215 help=help,
216 epilog=epilog,
217 short_help=short_help,
218 options_metavar=options_metavar,
219 add_help_option=add_help_option,
220 hidden=hidden,
221 deprecated=deprecated,
222 rich_help_panel=rich_help_panel,
223 )
224 return f 1iceafgbdh
226 return decorator 1iceafgbdh
228 def command( 1ceafgbdh
229 self,
230 name: Optional[str] = None,
231 *,
232 cls: Optional[Type[TyperCommand]] = None,
233 context_settings: Optional[Dict[Any, Any]] = None,
234 help: Optional[str] = None,
235 epilog: Optional[str] = None,
236 short_help: Optional[str] = None,
237 options_metavar: str = "[OPTIONS]",
238 add_help_option: bool = True,
239 no_args_is_help: bool = False,
240 hidden: bool = False,
241 deprecated: bool = False,
242 # Rich settings
243 rich_help_panel: Union[str, None] = Default(None),
244 ) -> Callable[[CommandFunctionType], CommandFunctionType]:
245 if cls is None: 1iceafgbdh
246 cls = TyperCommand 1iceafgbdh
248 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1iceafgbdh
249 self.registered_commands.append( 1iceafgbdh
250 CommandInfo(
251 name=name,
252 cls=cls,
253 context_settings=context_settings,
254 callback=f,
255 help=help,
256 epilog=epilog,
257 short_help=short_help,
258 options_metavar=options_metavar,
259 add_help_option=add_help_option,
260 no_args_is_help=no_args_is_help,
261 hidden=hidden,
262 deprecated=deprecated,
263 # Rich settings
264 rich_help_panel=rich_help_panel,
265 )
266 )
267 return f 1iceafgbdh
269 return decorator 1iceafgbdh
271 def add_typer( 1ceafgbdh
272 self,
273 typer_instance: "Typer",
274 *,
275 name: Optional[str] = Default(None),
276 cls: Optional[Type[TyperGroup]] = Default(None),
277 invoke_without_command: bool = Default(False),
278 no_args_is_help: bool = Default(False),
279 subcommand_metavar: Optional[str] = Default(None),
280 chain: bool = Default(False),
281 result_callback: Optional[Callable[..., Any]] = Default(None),
282 # Command
283 context_settings: Optional[Dict[Any, Any]] = Default(None),
284 callback: Optional[Callable[..., Any]] = Default(None),
285 help: Optional[str] = Default(None),
286 epilog: Optional[str] = Default(None),
287 short_help: Optional[str] = Default(None),
288 options_metavar: str = Default("[OPTIONS]"),
289 add_help_option: bool = Default(True),
290 hidden: bool = Default(False),
291 deprecated: bool = Default(False),
292 # Rich settings
293 rich_help_panel: Union[str, None] = Default(None),
294 ) -> None:
295 self.registered_groups.append( 1iceafgbdh
296 TyperInfo(
297 typer_instance,
298 name=name,
299 cls=cls,
300 invoke_without_command=invoke_without_command,
301 no_args_is_help=no_args_is_help,
302 subcommand_metavar=subcommand_metavar,
303 chain=chain,
304 result_callback=result_callback,
305 context_settings=context_settings,
306 callback=callback,
307 help=help,
308 epilog=epilog,
309 short_help=short_help,
310 options_metavar=options_metavar,
311 add_help_option=add_help_option,
312 hidden=hidden,
313 deprecated=deprecated,
314 rich_help_panel=rich_help_panel,
315 )
316 )
318 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1iceafgbdh
319 if sys.excepthook != except_hook: 1iceafgbdh
320 sys.excepthook = except_hook 1iceafgbdh
321 try: 1iceafgbdh
322 return get_command(self)(*args, **kwargs) 1iceafgbdh
323 except Exception as e: 1iceafgbdh
324 # Set a custom attribute to tell the hook to show nice exceptions for user
325 # code. An alternative/first implementation was a custom exception with
326 # raise custom_exc from e
327 # but that means the last error shown is the custom exception, not the
328 # actual error. This trick improves developer experience by showing the
329 # actual error last.
330 setattr( 1iceafgbdh
331 e,
332 _typer_developer_exception_attr_name,
333 DeveloperExceptionConfig(
334 pretty_exceptions_enable=self.pretty_exceptions_enable,
335 pretty_exceptions_show_locals=self.pretty_exceptions_show_locals,
336 pretty_exceptions_short=self.pretty_exceptions_short,
337 ),
338 )
339 raise e 1iceafgbdh
342def get_group(typer_instance: Typer) -> TyperGroup: 1iceafgbdh
343 group = get_group_from_info( 1iceafgbdh
344 TyperInfo(typer_instance),
345 pretty_exceptions_short=typer_instance.pretty_exceptions_short,
346 rich_markup_mode=typer_instance.rich_markup_mode,
347 )
348 return group 1iceafgbdh
351def get_command(typer_instance: Typer) -> click.Command: 1iceafgbdh
352 if typer_instance._add_completion: 1iceafgbdh
353 click_install_param, click_show_param = get_install_completion_arguments() 1iceafgbdh
354 if ( 1cabd
355 typer_instance.registered_callback
356 or typer_instance.info.callback
357 or typer_instance.registered_groups
358 or len(typer_instance.registered_commands) > 1
359 ):
360 # Create a Group
361 click_command: click.Command = get_group(typer_instance) 1iceafgbdh
362 if typer_instance._add_completion: 1iceafgbdh
363 click_command.params.append(click_install_param) 1iceafgbdh
364 click_command.params.append(click_show_param) 1iceafgbdh
365 return click_command 1iceafgbdh
366 elif len(typer_instance.registered_commands) == 1: 1iceafgbdh
367 # Create a single Command
368 single_command = typer_instance.registered_commands[0] 1iceafgbdh
370 if not single_command.context_settings and not isinstance( 1iceafgbdh
371 typer_instance.info.context_settings, DefaultPlaceholder
372 ):
373 single_command.context_settings = typer_instance.info.context_settings 1iceafgbdh
375 click_command = get_command_from_info( 1iceafgbdh
376 single_command,
377 pretty_exceptions_short=typer_instance.pretty_exceptions_short,
378 rich_markup_mode=typer_instance.rich_markup_mode,
379 )
380 if typer_instance._add_completion: 1iceafgbdh
381 click_command.params.append(click_install_param) 1iceafgbdh
382 click_command.params.append(click_show_param) 1iceafgbdh
383 return click_command 1iceafgbdh
384 raise RuntimeError(
385 "Could not get a command for this Typer instance"
386 ) # pragma: no cover
389def solve_typer_info_help(typer_info: TyperInfo) -> str: 1iceafgbdh
390 # Priority 1: Explicit value was set in app.add_typer()
391 if not isinstance(typer_info.help, DefaultPlaceholder): 1iceafgbdh
392 return inspect.cleandoc(typer_info.help or "") 1iceafgbdh
393 # Priority 2: Explicit value was set in sub_app.callback()
394 try: 1iceafgbdh
395 callback_help = typer_info.typer_instance.registered_callback.help 1iceafgbdh
396 if not isinstance(callback_help, DefaultPlaceholder): 1iceafgbdh
397 return inspect.cleandoc(callback_help or "") 1iceafgbdh
398 except AttributeError: 1iceafgbdh
399 pass 1iceafgbdh
400 # Priority 3: Explicit value was set in sub_app = typer.Typer()
401 try: 1iceafgbdh
402 instance_help = typer_info.typer_instance.info.help 1iceafgbdh
403 if not isinstance(instance_help, DefaultPlaceholder): 1iceafgbdh
404 return inspect.cleandoc(instance_help or "") 1iceafgbdh
405 except AttributeError: 1iceafgbdh
406 pass 1iceafgbdh
407 # Priority 4: Implicit inference from callback docstring in app.add_typer()
408 if typer_info.callback: 1iceafgbdh
409 doc = inspect.getdoc(typer_info.callback) 1iceafgbdh
410 if doc: 1iceafgbdh
411 return doc 1iceafgbdh
412 # Priority 5: Implicit inference from callback docstring in @app.callback()
413 try: 1iceafgbdh
414 callback = typer_info.typer_instance.registered_callback.callback 1iceafgbdh
415 if not isinstance(callback, DefaultPlaceholder): 1iceafgbdh
416 doc = inspect.getdoc(callback or "") 1iceafgbdh
417 if doc: 1iceafgbdh
418 return doc 1iceafgbdh
419 except AttributeError: 1iceafgbdh
420 pass 1iceafgbdh
421 # Priority 6: Implicit inference from callback docstring in typer.Typer()
422 try: 1iceafgbdh
423 instance_callback = typer_info.typer_instance.info.callback 1iceafgbdh
424 if not isinstance(instance_callback, DefaultPlaceholder): 1iceafgbdh
425 doc = inspect.getdoc(instance_callback) 1iceafgbdh
426 if doc: 1iceafgbdh
427 return doc 1iceafgbdh
428 except AttributeError: 1iceafgbdh
429 pass 1iceafgbdh
430 # Value not set, use the default
431 return typer_info.help.value 1iceafgbdh
434def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1iceafgbdh
435 values: Dict[str, Any] = {} 1iceafgbdh
436 for name, value in typer_info.__dict__.items(): 1iceafgbdh
437 # Priority 1: Value was set in app.add_typer()
438 if not isinstance(value, DefaultPlaceholder): 1iceafgbdh
439 values[name] = value 1iceafgbdh
440 continue 1iceafgbdh
441 # Priority 2: Value was set in @subapp.callback()
442 try: 1iceafgbdh
443 callback_value = getattr( 1iceafgbdh
444 typer_info.typer_instance.registered_callback, # type: ignore
445 name,
446 )
447 if not isinstance(callback_value, DefaultPlaceholder): 1iceafgbdh
448 values[name] = callback_value 1iceafgbdh
449 continue 1iceafgbdh
450 except AttributeError: 1iceafgbdh
451 pass 1iceafgbdh
452 # Priority 3: Value set in subapp = typer.Typer()
453 try: 1iceafgbdh
454 instance_value = getattr( 1iceafgbdh
455 typer_info.typer_instance.info, # type: ignore
456 name,
457 )
458 if not isinstance(instance_value, DefaultPlaceholder): 1iceafgbdh
459 values[name] = instance_value 1iceafgbdh
460 continue 1iceafgbdh
461 except AttributeError: 1iceafgbdh
462 pass 1iceafgbdh
463 # Value not set, use the default
464 values[name] = value.value 1iceafgbdh
465 values["help"] = solve_typer_info_help(typer_info) 1iceafgbdh
466 return TyperInfo(**values) 1iceafgbdh
469def get_group_from_info( 1ceafgbdh
470 group_info: TyperInfo,
471 *,
472 pretty_exceptions_short: bool,
473 rich_markup_mode: MarkupMode,
474) -> TyperGroup:
475 assert group_info.typer_instance, ( 1iceafgbdh
476 "A Typer instance is needed to generate a Click Group"
477 )
478 commands: Dict[str, click.Command] = {} 1iceafgbdh
479 for command_info in group_info.typer_instance.registered_commands: 1iceafgbdh
480 command = get_command_from_info( 1iceafgbdh
481 command_info=command_info,
482 pretty_exceptions_short=pretty_exceptions_short,
483 rich_markup_mode=rich_markup_mode,
484 )
485 if command.name: 1iceafgbdh
486 commands[command.name] = command 1iceafgbdh
487 for sub_group_info in group_info.typer_instance.registered_groups: 1iceafgbdh
488 sub_group = get_group_from_info( 1iceafgbdh
489 sub_group_info,
490 pretty_exceptions_short=pretty_exceptions_short,
491 rich_markup_mode=rich_markup_mode,
492 )
493 if sub_group.name: 1iceafgbdh
494 commands[sub_group.name] = sub_group 1iceafgbdh
495 else:
496 if sub_group.callback: 1iceafgbdh
497 import warnings 1iceafgbdh
499 warnings.warn( 1iceafgbdh
500 "The 'callback' parameter is not supported by Typer when using `add_typer` without a name",
501 stacklevel=5,
502 )
503 for sub_command_name, sub_command in sub_group.commands.items(): 1iceafgbdh
504 commands[sub_command_name] = sub_command 1iceafgbdh
505 solved_info = solve_typer_info_defaults(group_info) 1iceafgbdh
506 ( 1ceafgbdh
507 params,
508 convertors,
509 context_param_name,
510 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback)
511 cls = solved_info.cls or TyperGroup 1iceafgbdh
512 assert issubclass(cls, TyperGroup), f"{cls} should be a subclass of {TyperGroup}" 1iceafgbdh
513 group = cls( 1iceafgbdh
514 name=solved_info.name or "",
515 commands=commands,
516 invoke_without_command=solved_info.invoke_without_command,
517 no_args_is_help=solved_info.no_args_is_help,
518 subcommand_metavar=solved_info.subcommand_metavar,
519 chain=solved_info.chain,
520 result_callback=solved_info.result_callback,
521 context_settings=solved_info.context_settings,
522 callback=get_callback(
523 callback=solved_info.callback,
524 params=params,
525 convertors=convertors,
526 context_param_name=context_param_name,
527 pretty_exceptions_short=pretty_exceptions_short,
528 ),
529 params=params,
530 help=solved_info.help,
531 epilog=solved_info.epilog,
532 short_help=solved_info.short_help,
533 options_metavar=solved_info.options_metavar,
534 add_help_option=solved_info.add_help_option,
535 hidden=solved_info.hidden,
536 deprecated=solved_info.deprecated,
537 rich_markup_mode=rich_markup_mode,
538 # Rich settings
539 rich_help_panel=solved_info.rich_help_panel,
540 )
541 return group 1iceafgbdh
544def get_command_name(name: str) -> str: 1iceafgbdh
545 return name.lower().replace("_", "-") 1iceafgbdh
548def get_params_convertors_ctx_param_name_from_function( 1ceafgbdh
549 callback: Optional[Callable[..., Any]],
550) -> Tuple[List[Union[click.Argument, click.Option]], Dict[str, Any], Optional[str]]:
551 params = [] 1iceafgbdh
552 convertors = {} 1iceafgbdh
553 context_param_name = None 1iceafgbdh
554 if callback: 1iceafgbdh
555 parameters = get_params_from_function(callback) 1iceafgbdh
556 for param_name, param in parameters.items(): 1iceafgbdh
557 if lenient_issubclass(param.annotation, click.Context): 1iceafgbdh
558 context_param_name = param_name 1iceafgbdh
559 continue 1iceafgbdh
560 click_param, convertor = get_click_param(param) 1iceafgbdh
561 if convertor: 1iceafgbdh
562 convertors[param_name] = convertor 1iceafgbdh
563 params.append(click_param) 1iceafgbdh
564 return params, convertors, context_param_name 1iceafgbdh
567def get_command_from_info( 1ceafgbdh
568 command_info: CommandInfo,
569 *,
570 pretty_exceptions_short: bool,
571 rich_markup_mode: MarkupMode,
572) -> click.Command:
573 assert command_info.callback, "A command must have a callback function" 1iceafgbdh
574 name = command_info.name or get_command_name(command_info.callback.__name__) 1iceafgbdh
575 use_help = command_info.help 1iceafgbdh
576 if use_help is None: 1iceafgbdh
577 use_help = inspect.getdoc(command_info.callback) 1iceafgbdh
578 else:
579 use_help = inspect.cleandoc(use_help) 1iceafgbdh
580 ( 1ceafgbdh
581 params,
582 convertors,
583 context_param_name,
584 ) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
585 cls = command_info.cls or TyperCommand 1iceafgbdh
586 command = cls( 1iceafgbdh
587 name=name,
588 context_settings=command_info.context_settings,
589 callback=get_callback(
590 callback=command_info.callback,
591 params=params,
592 convertors=convertors,
593 context_param_name=context_param_name,
594 pretty_exceptions_short=pretty_exceptions_short,
595 ),
596 params=params, # type: ignore
597 help=use_help,
598 epilog=command_info.epilog,
599 short_help=command_info.short_help,
600 options_metavar=command_info.options_metavar,
601 add_help_option=command_info.add_help_option,
602 no_args_is_help=command_info.no_args_is_help,
603 hidden=command_info.hidden,
604 deprecated=command_info.deprecated,
605 rich_markup_mode=rich_markup_mode,
606 # Rich settings
607 rich_help_panel=command_info.rich_help_panel,
608 )
609 return command 1iceafgbdh
612def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]: 1iceafgbdh
613 convertor: Optional[Callable[[Any], Any]] = None 1iceafgbdh
614 if lenient_issubclass(type_, Path): 1iceafgbdh
615 convertor = param_path_convertor 1iceafgbdh
616 if lenient_issubclass(type_, Enum): 1iceafgbdh
617 convertor = generate_enum_convertor(type_) 1iceafgbdh
618 return convertor 1iceafgbdh
621def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: 1iceafgbdh
622 if value is not None: 1iceafgbdh
623 return Path(value) 1iceafgbdh
624 return None 1iceafgbdh
627def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]: 1iceafgbdh
628 val_map = {str(val.value): val for val in enum} 1iceafgbdh
630 def convertor(value: Any) -> Any: 1iceafgbdh
631 if value is not None: 1iceafgbdh
632 val = str(value) 1iceafgbdh
633 if val in val_map: 1iceafgbdh
634 key = val_map[val] 1iceafgbdh
635 return enum(key) 1iceafgbdh
637 return convertor 1iceafgbdh
640def generate_list_convertor( 1ceafgbdh
641 convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any]
642) -> Callable[[Sequence[Any]], Optional[List[Any]]]:
643 def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]: 1iceafgbdh
644 if default_value is None and len(value) == 0: 1iceafgbdh
645 return None 1iceafgbdh
646 return [convertor(v) if convertor else v for v in value] 1iceafgbdh
648 return internal_convertor 1iceafgbdh
651def generate_tuple_convertor( 1ceafgbdh
652 types: Sequence[Any],
653) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]:
654 convertors = [determine_type_convertor(type_) for type_ in types] 1iceafgbdh
656 def internal_convertor( 1ceafgbdh
657 param_args: Optional[Tuple[Any, ...]],
658 ) -> Optional[Tuple[Any, ...]]:
659 if param_args is None: 1iceafgbdh
660 return None 1iceafgbdh
661 return tuple( 1iceafgbdh
662 convertor(arg) if convertor else arg
663 for (convertor, arg) in zip(convertors, param_args)
664 )
666 return internal_convertor 1iceafgbdh
669def get_callback( 1ceafgbdh
670 *,
671 callback: Optional[Callable[..., Any]] = None,
672 params: Sequence[click.Parameter] = [],
673 convertors: Optional[Dict[str, Callable[[str], Any]]] = None,
674 context_param_name: Optional[str] = None,
675 pretty_exceptions_short: bool,
676) -> Optional[Callable[..., Any]]:
677 use_convertors = convertors or {} 1iceafgbdh
678 if not callback: 1iceafgbdh
679 return None 1iceafgbdh
680 parameters = get_params_from_function(callback) 1iceafgbdh
681 use_params: Dict[str, Any] = {} 1iceafgbdh
682 for param_name in parameters: 1iceafgbdh
683 use_params[param_name] = None 1iceafgbdh
684 for param in params: 1iceafgbdh
685 if param.name: 1iceafgbdh
686 use_params[param.name] = param.default 1iceafgbdh
688 def wrapper(**kwargs: Any) -> Any: 1iceafgbdh
689 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1iceafgbdh
690 for k, v in kwargs.items(): 1iceafgbdh
691 if k in use_convertors: 1iceafgbdh
692 use_params[k] = use_convertors[k](v) 1iceafgbdh
693 else:
694 use_params[k] = v 1iceafgbdh
695 if context_param_name: 1iceafgbdh
696 use_params[context_param_name] = click.get_current_context() 1iceafgbdh
697 return callback(**use_params) 1iceafgbdh
699 update_wrapper(wrapper, callback) 1iceafgbdh
700 return wrapper 1iceafgbdh
703def get_click_type( 1ceafgbdh
704 *, annotation: Any, parameter_info: ParameterInfo
705) -> click.ParamType:
706 if parameter_info.click_type is not None: 1iceafgbdh
707 return parameter_info.click_type 1iceafgbdh
709 elif parameter_info.parser is not None: 1iceafgbdh
710 return click.types.FuncParamType(parameter_info.parser) 1iceafgbdh
712 elif annotation is str: 1iceafgbdh
713 return click.STRING 1iceafgbdh
714 elif annotation is int: 1iceafgbdh
715 if parameter_info.min is not None or parameter_info.max is not None: 1iceafgbdh
716 min_ = None 1iceafgbdh
717 max_ = None 1iceafgbdh
718 if parameter_info.min is not None: 1iceafgbdh
719 min_ = int(parameter_info.min) 1iceafgbdh
720 if parameter_info.max is not None: 1iceafgbdh
721 max_ = int(parameter_info.max) 1iceafgbdh
722 return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1iceafgbdh
723 else:
724 return click.INT 1iceafgbdh
725 elif annotation is float: 1iceafgbdh
726 if parameter_info.min is not None or parameter_info.max is not None: 1iceafgbdh
727 return click.FloatRange( 1iceafgbdh
728 min=parameter_info.min,
729 max=parameter_info.max,
730 clamp=parameter_info.clamp,
731 )
732 else:
733 return click.FLOAT 1iceafgbdh
734 elif annotation is bool: 1iceafgbdh
735 return click.BOOL 1iceafgbdh
736 elif annotation == UUID: 1iceafgbdh
737 return click.UUID 1iceafgbdh
738 elif annotation == datetime: 1iceafgbdh
739 return click.DateTime(formats=parameter_info.formats) 1iceafgbdh
740 elif ( 1ab
741 annotation == Path
742 or parameter_info.allow_dash
743 or parameter_info.path_type
744 or parameter_info.resolve_path
745 ):
746 return click.Path( 1iceafgbdh
747 exists=parameter_info.exists,
748 file_okay=parameter_info.file_okay,
749 dir_okay=parameter_info.dir_okay,
750 writable=parameter_info.writable,
751 readable=parameter_info.readable,
752 resolve_path=parameter_info.resolve_path,
753 allow_dash=parameter_info.allow_dash,
754 path_type=parameter_info.path_type,
755 )
756 elif lenient_issubclass(annotation, FileTextWrite): 1iceafgbdh
757 return click.File( 1iceafgbdh
758 mode=parameter_info.mode or "w",
759 encoding=parameter_info.encoding,
760 errors=parameter_info.errors,
761 lazy=parameter_info.lazy,
762 atomic=parameter_info.atomic,
763 )
764 elif lenient_issubclass(annotation, FileText): 1iceafgbdh
765 return click.File( 1iceafgbdh
766 mode=parameter_info.mode or "r",
767 encoding=parameter_info.encoding,
768 errors=parameter_info.errors,
769 lazy=parameter_info.lazy,
770 atomic=parameter_info.atomic,
771 )
772 elif lenient_issubclass(annotation, FileBinaryRead): 1iceafgbdh
773 return click.File( 1iceafgbdh
774 mode=parameter_info.mode or "rb",
775 encoding=parameter_info.encoding,
776 errors=parameter_info.errors,
777 lazy=parameter_info.lazy,
778 atomic=parameter_info.atomic,
779 )
780 elif lenient_issubclass(annotation, FileBinaryWrite): 1iceafgbdh
781 return click.File( 1iceafgbdh
782 mode=parameter_info.mode or "wb",
783 encoding=parameter_info.encoding,
784 errors=parameter_info.errors,
785 lazy=parameter_info.lazy,
786 atomic=parameter_info.atomic,
787 )
788 elif lenient_issubclass(annotation, Enum): 1iceafgbdh
789 return click.Choice( 1iceafgbdh
790 [item.value for item in annotation],
791 case_sensitive=parameter_info.case_sensitive,
792 )
793 raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover
796def lenient_issubclass( 1ceafgbdh
797 cls: Any, class_or_tuple: Union[AnyType, Tuple[AnyType, ...]]
798) -> bool:
799 return isinstance(cls, type) and issubclass(cls, class_or_tuple) 1iceafgbdh
802def get_click_param( 1ceafgbdh
803 param: ParamMeta,
804) -> Tuple[Union[click.Argument, click.Option], Any]:
805 # First, find out what will be:
806 # * ParamInfo (ArgumentInfo or OptionInfo)
807 # * default_value
808 # * required
809 default_value = None 1iceafgbdh
810 required = False 1iceafgbdh
811 if isinstance(param.default, ParameterInfo): 1iceafgbdh
812 parameter_info = param.default 1iceafgbdh
813 if parameter_info.default == Required: 1iceafgbdh
814 required = True 1iceafgbdh
815 else:
816 default_value = parameter_info.default 1iceafgbdh
817 elif param.default == Required or param.default is param.empty: 1iceafgbdh
818 required = True 1iceafgbdh
819 parameter_info = ArgumentInfo() 1iceafgbdh
820 else:
821 default_value = param.default 1iceafgbdh
822 parameter_info = OptionInfo() 1iceafgbdh
823 annotation: Any
824 if param.annotation is not param.empty: 1iceafgbdh
825 annotation = param.annotation 1iceafgbdh
826 else:
827 annotation = str 1iceafgbdh
828 main_type = annotation 1iceafgbdh
829 is_list = False 1iceafgbdh
830 is_tuple = False 1iceafgbdh
831 parameter_type: Any = None 1iceafgbdh
832 is_flag = None 1iceafgbdh
833 origin = get_origin(main_type) 1iceafgbdh
835 if origin is not None: 1iceafgbdh
836 # Handle SomeType | None and Optional[SomeType]
837 if is_union(origin): 1iceafgbdh
838 types = [] 1iceafgbdh
839 for type_ in get_args(main_type): 1iceafgbdh
840 if type_ is NoneType: 1iceafgbdh
841 continue 1iceafgbdh
842 types.append(type_) 1iceafgbdh
843 assert len(types) == 1, "Typer Currently doesn't support Union types" 1iceafgbdh
844 main_type = types[0] 1iceafgbdh
845 origin = get_origin(main_type) 1iceafgbdh
846 # Handle Tuples and Lists
847 if lenient_issubclass(origin, List): 1iceafgbdh
848 main_type = get_args(main_type)[0] 1iceafgbdh
849 assert not get_origin(main_type), ( 1iceafgbdh
850 "List types with complex sub-types are not currently supported"
851 )
852 is_list = True 1iceafgbdh
853 elif lenient_issubclass(origin, Tuple): # type: ignore 1iceafgbdh
854 types = [] 1iceafgbdh
855 for type_ in get_args(main_type): 1iceafgbdh
856 assert not get_origin(type_), ( 1iceafgbdh
857 "Tuple types with complex sub-types are not currently supported"
858 )
859 types.append( 1iceafgbdh
860 get_click_type(annotation=type_, parameter_info=parameter_info)
861 )
862 parameter_type = tuple(types) 1iceafgbdh
863 is_tuple = True 1iceafgbdh
864 if parameter_type is None: 1iceafgbdh
865 parameter_type = get_click_type( 1iceafgbdh
866 annotation=main_type, parameter_info=parameter_info
867 )
868 convertor = determine_type_convertor(main_type) 1iceafgbdh
869 if is_list: 1iceafgbdh
870 convertor = generate_list_convertor( 1iceafgbdh
871 convertor=convertor, default_value=default_value
872 )
873 if is_tuple: 1iceafgbdh
874 convertor = generate_tuple_convertor(get_args(main_type)) 1iceafgbdh
875 if isinstance(parameter_info, OptionInfo): 1iceafgbdh
876 if main_type is bool: 1iceafgbdh
877 is_flag = True 1iceafgbdh
878 # Click doesn't accept a flag of type bool, only None, and then it sets it
879 # to bool internally
880 parameter_type = None 1iceafgbdh
881 default_option_name = get_command_name(param.name) 1iceafgbdh
882 if is_flag: 1iceafgbdh
883 default_option_declaration = ( 1ceafgbdh
884 f"--{default_option_name}/--no-{default_option_name}"
885 )
886 else:
887 default_option_declaration = f"--{default_option_name}" 1iceafgbdh
888 param_decls = [param.name] 1iceafgbdh
889 if parameter_info.param_decls: 1iceafgbdh
890 param_decls.extend(parameter_info.param_decls) 1iceafgbdh
891 else:
892 param_decls.append(default_option_declaration) 1iceafgbdh
893 return ( 1ceafgbdh
894 TyperOption(
895 # Option
896 param_decls=param_decls,
897 show_default=parameter_info.show_default,
898 prompt=parameter_info.prompt,
899 confirmation_prompt=parameter_info.confirmation_prompt,
900 prompt_required=parameter_info.prompt_required,
901 hide_input=parameter_info.hide_input,
902 is_flag=is_flag,
903 multiple=is_list,
904 count=parameter_info.count,
905 allow_from_autoenv=parameter_info.allow_from_autoenv,
906 type=parameter_type,
907 help=parameter_info.help,
908 hidden=parameter_info.hidden,
909 show_choices=parameter_info.show_choices,
910 show_envvar=parameter_info.show_envvar,
911 # Parameter
912 required=required,
913 default=default_value,
914 callback=get_param_callback(
915 callback=parameter_info.callback, convertor=convertor
916 ),
917 metavar=parameter_info.metavar,
918 expose_value=parameter_info.expose_value,
919 is_eager=parameter_info.is_eager,
920 envvar=parameter_info.envvar,
921 shell_complete=parameter_info.shell_complete,
922 autocompletion=get_param_completion(parameter_info.autocompletion),
923 # Rich settings
924 rich_help_panel=parameter_info.rich_help_panel,
925 ),
926 convertor,
927 )
928 elif isinstance(parameter_info, ArgumentInfo): 1iceafgbdh
929 param_decls = [param.name] 1iceafgbdh
930 nargs = None 1iceafgbdh
931 if is_list: 1iceafgbdh
932 nargs = -1 1iceafgbdh
933 return ( 1ceafgbdh
934 TyperArgument(
935 # Argument
936 param_decls=param_decls,
937 type=parameter_type,
938 required=required,
939 nargs=nargs,
940 # TyperArgument
941 show_default=parameter_info.show_default,
942 show_choices=parameter_info.show_choices,
943 show_envvar=parameter_info.show_envvar,
944 help=parameter_info.help,
945 hidden=parameter_info.hidden,
946 # Parameter
947 default=default_value,
948 callback=get_param_callback(
949 callback=parameter_info.callback, convertor=convertor
950 ),
951 metavar=parameter_info.metavar,
952 expose_value=parameter_info.expose_value,
953 is_eager=parameter_info.is_eager,
954 envvar=parameter_info.envvar,
955 shell_complete=parameter_info.shell_complete,
956 autocompletion=get_param_completion(parameter_info.autocompletion),
957 # Rich settings
958 rich_help_panel=parameter_info.rich_help_panel,
959 ),
960 convertor,
961 )
962 raise AssertionError("A click.Parameter should be returned") # pragma: no cover
965def get_param_callback( 1ceafgbdh
966 *,
967 callback: Optional[Callable[..., Any]] = None,
968 convertor: Optional[Callable[..., Any]] = None,
969) -> Optional[Callable[..., Any]]:
970 if not callback: 1iceafgbdh
971 return None 1iceafgbdh
972 parameters = get_params_from_function(callback) 1iceafgbdh
973 ctx_name = None 1iceafgbdh
974 click_param_name = None 1iceafgbdh
975 value_name = None 1iceafgbdh
976 untyped_names: List[str] = [] 1iceafgbdh
977 for param_name, param_sig in parameters.items(): 1iceafgbdh
978 if lenient_issubclass(param_sig.annotation, click.Context): 1iceafgbdh
979 ctx_name = param_name 1iceafgbdh
980 elif lenient_issubclass(param_sig.annotation, click.Parameter): 1iceafgbdh
981 click_param_name = param_name 1iceafgbdh
982 else:
983 untyped_names.append(param_name) 1iceafgbdh
984 # Extract value param name first
985 if untyped_names: 1iceafgbdh
986 value_name = untyped_names.pop() 1iceafgbdh
987 # If context and Click param were not typed (old/Click callback style) extract them
988 if untyped_names: 1iceafgbdh
989 if ctx_name is None: 1iceafgbdh
990 ctx_name = untyped_names.pop(0) 1iceafgbdh
991 if click_param_name is None: 1iceafgbdh
992 if untyped_names: 1iceafgbdh
993 click_param_name = untyped_names.pop(0) 1iceafgbdh
994 if untyped_names: 1iceafgbdh
995 raise click.ClickException( 1iceafgbdh
996 "Too many CLI parameter callback function parameters"
997 )
999 def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: 1iceafgbdh
1000 use_params: Dict[str, Any] = {} 1iceafgbdh
1001 if ctx_name: 1iceafgbdh
1002 use_params[ctx_name] = ctx 1iceafgbdh
1003 if click_param_name: 1iceafgbdh
1004 use_params[click_param_name] = param 1iceafgbdh
1005 if value_name: 1iceafgbdh
1006 if convertor: 1iceafgbdh
1007 use_value = convertor(value) 1iceafgbdh
1008 else:
1009 use_value = value 1iceafgbdh
1010 use_params[value_name] = use_value 1iceafgbdh
1011 return callback(**use_params) 1iceafgbdh
1013 update_wrapper(wrapper, callback) 1iceafgbdh
1014 return wrapper 1iceafgbdh
1017def get_param_completion( 1ceafgbdh
1018 callback: Optional[Callable[..., Any]] = None,
1019) -> Optional[Callable[..., Any]]:
1020 if not callback: 1iceafgbdh
1021 return None 1iceafgbdh
1022 parameters = get_params_from_function(callback) 1iceafgbdh
1023 ctx_name = None 1iceafgbdh
1024 args_name = None 1iceafgbdh
1025 incomplete_name = None 1iceafgbdh
1026 unassigned_params = list(parameters.values()) 1iceafgbdh
1027 for param_sig in unassigned_params[:]: 1iceafgbdh
1028 origin = get_origin(param_sig.annotation) 1iceafgbdh
1029 if lenient_issubclass(param_sig.annotation, click.Context): 1iceafgbdh
1030 ctx_name = param_sig.name 1iceafgbdh
1031 unassigned_params.remove(param_sig) 1iceafgbdh
1032 elif lenient_issubclass(origin, List): 1iceafgbdh
1033 args_name = param_sig.name 1iceafgbdh
1034 unassigned_params.remove(param_sig) 1iceafgbdh
1035 elif lenient_issubclass(param_sig.annotation, str): 1iceafgbdh
1036 incomplete_name = param_sig.name 1iceafgbdh
1037 unassigned_params.remove(param_sig) 1iceafgbdh
1038 # If there are still unassigned parameters (not typed), extract by name
1039 for param_sig in unassigned_params[:]: 1iceafgbdh
1040 if ctx_name is None and param_sig.name == "ctx": 1iceafgbdh
1041 ctx_name = param_sig.name 1iceafgbdh
1042 unassigned_params.remove(param_sig) 1iceafgbdh
1043 elif args_name is None and param_sig.name == "args": 1iceafgbdh
1044 args_name = param_sig.name 1iceafgbdh
1045 unassigned_params.remove(param_sig) 1iceafgbdh
1046 elif incomplete_name is None and param_sig.name == "incomplete": 1iceafgbdh
1047 incomplete_name = param_sig.name 1iceafgbdh
1048 unassigned_params.remove(param_sig) 1iceafgbdh
1049 # Extract value param name first
1050 if unassigned_params: 1iceafgbdh
1051 show_params = " ".join([param.name for param in unassigned_params]) 1iceafgbdh
1052 raise click.ClickException( 1iceafgbdh
1053 f"Invalid autocompletion callback parameters: {show_params}"
1054 )
1056 def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any: 1iceafgbdh
1057 use_params: Dict[str, Any] = {} 1iceafgbdh
1058 if ctx_name: 1iceafgbdh
1059 use_params[ctx_name] = ctx 1iceafgbdh
1060 if args_name: 1iceafgbdh
1061 use_params[args_name] = args 1iceafgbdh
1062 if incomplete_name: 1iceafgbdh
1063 use_params[incomplete_name] = incomplete 1iceafgbdh
1064 return callback(**use_params) 1iceafgbdh
1066 update_wrapper(wrapper, callback) 1iceafgbdh
1067 return wrapper 1iceafgbdh
1070def run(function: Callable[..., Any]) -> None: 1iceafgbdh
1071 app = Typer(add_completion=False) 1iceafgbdh
1072 app.command()(function) 1iceafgbdh
1073 app() 1iceafgbdh
1076def _is_macos() -> bool: 1iceafgbdh
1077 return platform.system() == "Darwin" 1iceafgbdh
1080def _is_linux_or_bsd() -> bool: 1iceafgbdh
1081 if platform.system() == "Linux": 1iceafgbdh
1082 return True 1iceafgbdh
1084 return "BSD" in platform.system() 1iceafgbdh
1087def launch(url: str, wait: bool = False, locate: bool = False) -> int: 1iceafgbdh
1088 """This function launches the given URL (or filename) in the default
1089 viewer application for this file type. If this is an executable, it
1090 might launch the executable in a new session. The return value is
1091 the exit code of the launched application. Usually, ``0`` indicates
1092 success.
1094 This function handles url in different operating systems separately:
1095 - On macOS (Darwin), it uses the 'open' command.
1096 - On Linux and BSD, it uses 'xdg-open' if available.
1097 - On Windows (and other OSes), it uses the standard webbrowser module.
1099 The function avoids, when possible, using the webbrowser module on Linux and macOS
1100 to prevent spammy terminal messages from some browsers (e.g., Chrome).
1102 Examples::
1104 typer.launch("https://typer.tiangolo.com/")
1105 typer.launch("/my/downloaded/file", locate=True)
1107 :param url: URL or filename of the thing to launch.
1108 :param wait: Wait for the program to exit before returning. This
1109 only works if the launched program blocks. In particular,
1110 ``xdg-open`` on Linux does not block.
1111 :param locate: if this is set to `True` then instead of launching the
1112 application associated with the URL it will attempt to
1113 launch a file manager with the file located. This
1114 might have weird effects if the URL does not point to
1115 the filesystem.
1116 """
1118 if url.startswith("http://") or url.startswith("https://"): 1iceafgbdh
1119 if _is_macos(): 1iceafgbdh
1120 return subprocess.Popen( 1iceafgbdh
1121 ["open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
1122 ).wait()
1124 has_xdg_open = _is_linux_or_bsd() and shutil.which("xdg-open") is not None 1iceafgbdh
1126 if has_xdg_open: 1iceafgbdh
1127 return subprocess.Popen( 1iceafgbdh
1128 ["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
1129 ).wait()
1131 import webbrowser 1iceafgbdh
1133 webbrowser.open(url) 1iceafgbdh
1135 return 0 1iceafgbdh
1137 else:
1138 return click.launch(url) 1iceafgbdh