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