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