Coverage for typer/main.py: 100%
480 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-09 18:26 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-09 18:26 +0000
1import inspect 1hceafbdg
2import os 1hceafbdg
3import sys 1hceafbdg
4import traceback 1hceafbdg
5from datetime import datetime 1hceafbdg
6from enum import Enum 1hceafbdg
7from functools import update_wrapper 1hceafbdg
8from pathlib import Path 1hceafbdg
9from traceback import FrameSummary, StackSummary 1hceafbdg
10from types import TracebackType 1hceafbdg
11from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union 1hceafbdg
12from uuid import UUID 1hceafbdg
14import click 1hceafbdg
15from typing_extensions import get_args, get_origin 1hceafbdg
17from ._typing import is_union 1hceafbdg
18from .completion import get_completion_inspect_parameters 1hceafbdg
19from .core import ( 1hceafbdg
20 DEFAULT_MARKUP_MODE,
21 MarkupMode,
22 TyperArgument,
23 TyperCommand,
24 TyperGroup,
25 TyperOption,
26)
27from .models import ( 1hceafbdg
28 AnyType,
29 ArgumentInfo,
30 CommandFunctionType,
31 CommandInfo,
32 Default,
33 DefaultPlaceholder,
34 DeveloperExceptionConfig,
35 FileBinaryRead,
36 FileBinaryWrite,
37 FileText,
38 FileTextWrite,
39 NoneType,
40 OptionInfo,
41 ParameterInfo,
42 ParamMeta,
43 Required,
44 TyperInfo,
45)
46from .utils import get_params_from_function 1hceafbdg
48try: 1hceafbdg
49 import rich 1hceafbdg
50 from rich.traceback import Traceback 1hceafbdg
52 from . import rich_utils 1hceafbdg
54 console_stderr = rich_utils._get_rich_console(stderr=True) 1hceafbdg
56except ImportError: # pragma: no cover
57 rich = None # type: ignore
59_original_except_hook = sys.excepthook 1hceafbdg
60_typer_developer_exception_attr_name = "__typer_developer_exception__" 1hceafbdg
63def except_hook( 1ceafbdg
64 exc_type: Type[BaseException], exc_value: BaseException, tb: Optional[TracebackType]
65) -> None:
66 exception_config: Union[DeveloperExceptionConfig, None] = getattr( 1hceafbdg
67 exc_value, _typer_developer_exception_attr_name, None
68 )
69 standard_traceback = os.getenv("_TYPER_STANDARD_TRACEBACK") 1hceafbdg
70 if ( 1cabd
71 standard_traceback
72 or not exception_config
73 or not exception_config.pretty_exceptions_enable
74 ):
75 _original_except_hook(exc_type, exc_value, tb) 1hceafbdg
76 return 1hceafbdg
77 typer_path = os.path.dirname(__file__) 1hceafbdg
78 click_path = os.path.dirname(click.__file__) 1hceafbdg
79 supress_internal_dir_names = [typer_path, click_path] 1hceafbdg
80 exc = exc_value 1hceafbdg
81 if rich: 1hceafbdg
82 from .rich_utils import MAX_WIDTH 1hceafbdg
84 rich_tb = Traceback.from_exception( 1hceafbdg
85 type(exc),
86 exc,
87 exc.__traceback__,
88 show_locals=exception_config.pretty_exceptions_show_locals,
89 suppress=supress_internal_dir_names,
90 width=MAX_WIDTH,
91 )
92 console_stderr.print(rich_tb) 1hceafbdg
93 return 1hceafbdg
94 tb_exc = traceback.TracebackException.from_exception(exc) 1hceafbdg
95 stack: List[FrameSummary] = [] 1hceafbdg
96 for frame in tb_exc.stack: 1hceafbdg
97 if any(frame.filename.startswith(path) for path in supress_internal_dir_names): 1hceafbdg
98 if not exception_config.pretty_exceptions_short: 1hceafbdg
99 # Hide the line for internal libraries, Typer and Click
100 stack.append( 1hceafbdg
101 traceback.FrameSummary(
102 filename=frame.filename,
103 lineno=frame.lineno,
104 name=frame.name,
105 line="",
106 )
107 )
108 else:
109 stack.append(frame) 1hceafbdg
110 # Type ignore ref: https://github.com/python/typeshed/pull/8244
111 final_stack_summary = StackSummary.from_list(stack) 1hceafbdg
112 tb_exc.stack = final_stack_summary 1hceafbdg
113 for line in tb_exc.format(): 1hceafbdg
114 print(line, file=sys.stderr) 1hceafbdg
115 return 1hceafbdg
118def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]: 1hceafbdg
119 install_param, show_param = get_completion_inspect_parameters() 1hceafbdg
120 click_install_param, _ = get_click_param(install_param) 1hceafbdg
121 click_show_param, _ = get_click_param(show_param) 1hceafbdg
122 return click_install_param, click_show_param 1hceafbdg
125class Typer: 1hceafbdg
126 def __init__( 1ceafbdg
127 self,
128 *,
129 name: Optional[str] = Default(None),
130 cls: Optional[Type[TyperGroup]] = Default(None),
131 invoke_without_command: bool = Default(False),
132 no_args_is_help: bool = Default(False),
133 subcommand_metavar: Optional[str] = Default(None),
134 chain: bool = Default(False),
135 result_callback: Optional[Callable[..., Any]] = Default(None),
136 # Command
137 context_settings: Optional[Dict[Any, Any]] = Default(None),
138 callback: Optional[Callable[..., Any]] = Default(None),
139 help: Optional[str] = Default(None),
140 epilog: Optional[str] = Default(None),
141 short_help: Optional[str] = Default(None),
142 options_metavar: str = Default("[OPTIONS]"),
143 add_help_option: bool = Default(True),
144 hidden: bool = Default(False),
145 deprecated: bool = Default(False),
146 add_completion: bool = True,
147 # Rich settings
148 rich_markup_mode: MarkupMode = Default(DEFAULT_MARKUP_MODE),
149 rich_help_panel: Union[str, None] = Default(None),
150 pretty_exceptions_enable: bool = True,
151 pretty_exceptions_show_locals: bool = True,
152 pretty_exceptions_short: bool = True,
153 ):
154 self._add_completion = add_completion 1hceafbdg
155 self.rich_markup_mode: MarkupMode = rich_markup_mode 1hceafbdg
156 self.rich_help_panel = rich_help_panel 1hceafbdg
157 self.pretty_exceptions_enable = pretty_exceptions_enable 1hceafbdg
158 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1hceafbdg
159 self.pretty_exceptions_short = pretty_exceptions_short 1hceafbdg
160 self.info = TyperInfo( 1hceafbdg
161 name=name,
162 cls=cls,
163 invoke_without_command=invoke_without_command,
164 no_args_is_help=no_args_is_help,
165 subcommand_metavar=subcommand_metavar,
166 chain=chain,
167 result_callback=result_callback,
168 context_settings=context_settings,
169 callback=callback,
170 help=help,
171 epilog=epilog,
172 short_help=short_help,
173 options_metavar=options_metavar,
174 add_help_option=add_help_option,
175 hidden=hidden,
176 deprecated=deprecated,
177 )
178 self.registered_groups: List[TyperInfo] = [] 1hceafbdg
179 self.registered_commands: List[CommandInfo] = [] 1hceafbdg
180 self.registered_callback: Optional[TyperInfo] = None 1hceafbdg
182 def callback( 1ceafbdg
183 self,
184 name: Optional[str] = Default(None),
185 *,
186 cls: Optional[Type[TyperGroup]] = Default(None),
187 invoke_without_command: bool = Default(False),
188 no_args_is_help: bool = Default(False),
189 subcommand_metavar: Optional[str] = Default(None),
190 chain: bool = Default(False),
191 result_callback: Optional[Callable[..., Any]] = Default(None),
192 # Command
193 context_settings: Optional[Dict[Any, Any]] = Default(None),
194 help: Optional[str] = Default(None),
195 epilog: Optional[str] = Default(None),
196 short_help: Optional[str] = Default(None),
197 options_metavar: str = Default("[OPTIONS]"),
198 add_help_option: bool = Default(True),
199 hidden: bool = Default(False),
200 deprecated: bool = Default(False),
201 # Rich settings
202 rich_help_panel: Union[str, None] = Default(None),
203 ) -> Callable[[CommandFunctionType], CommandFunctionType]:
204 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1hceafbdg
205 self.registered_callback = TyperInfo( 1hceafbdg
206 name=name,
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 1hceafbdg
226 return decorator 1hceafbdg
228 def command( 1ceafbdg
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: 1hceafbdg
246 cls = TyperCommand 1hceafbdg
248 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1hceafbdg
249 self.registered_commands.append( 1hceafbdg
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 1hceafbdg
269 return decorator 1hceafbdg
271 def add_typer( 1ceafbdg
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( 1hceafbdg
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: 1hceafbdg
319 if sys.excepthook != except_hook: 1hceafbdg
320 sys.excepthook = except_hook 1hceafbdg
321 try: 1hceafbdg
322 return get_command(self)(*args, **kwargs) 1hceafbdg
323 except Exception as e: 1hceafbdg
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( 1hceafbdg
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 1hceafbdg
342def get_group(typer_instance: Typer) -> TyperGroup: 1hceafbdg
343 group = get_group_from_info( 1hceafbdg
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 1hceafbdg
351def get_command(typer_instance: Typer) -> click.Command: 1hceafbdg
352 if typer_instance._add_completion: 1hceafbdg
353 click_install_param, click_show_param = get_install_completion_arguments() 1hceafbdg
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) 1hceafbdg
362 if typer_instance._add_completion: 1hceafbdg
363 click_command.params.append(click_install_param) 1hceafbdg
364 click_command.params.append(click_show_param) 1hceafbdg
365 return click_command 1hceafbdg
366 elif len(typer_instance.registered_commands) == 1: 1hceafbdg
367 # Create a single Command
368 single_command = typer_instance.registered_commands[0] 1hceafbdg
370 if not single_command.context_settings and not isinstance( 1hceafbdg
371 typer_instance.info.context_settings, DefaultPlaceholder
372 ):
373 single_command.context_settings = typer_instance.info.context_settings 1hceafbdg
375 click_command = get_command_from_info( 1hceafbdg
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: 1hceafbdg
381 click_command.params.append(click_install_param) 1hceafbdg
382 click_command.params.append(click_show_param) 1hceafbdg
383 return click_command 1hceafbdg
384 raise RuntimeError(
385 "Could not get a command for this Typer instance"
386 ) # pragma: no cover
389def get_group_name(typer_info: TyperInfo) -> Optional[str]: 1hceafbdg
390 if typer_info.callback: 1hceafbdg
391 # Priority 1: Callback passed in app.add_typer()
392 return get_command_name(typer_info.callback.__name__) 1hceafbdg
393 if typer_info.typer_instance: 1hceafbdg
394 registered_callback = typer_info.typer_instance.registered_callback 1hceafbdg
395 if registered_callback: 1hceafbdg
396 if registered_callback.callback: 1hceafbdg
397 # Priority 2: Callback passed in @subapp.callback()
398 return get_command_name(registered_callback.callback.__name__) 1hceafbdg
399 if typer_info.typer_instance.info.callback: 1hceafbdg
400 return get_command_name(typer_info.typer_instance.info.callback.__name__) 1hceafbdg
401 return None 1hceafbdg
404def solve_typer_info_help(typer_info: TyperInfo) -> str: 1hceafbdg
405 # Priority 1: Explicit value was set in app.add_typer()
406 if not isinstance(typer_info.help, DefaultPlaceholder): 1hceafbdg
407 return inspect.cleandoc(typer_info.help or "") 1hceafbdg
408 # Priority 2: Explicit value was set in sub_app.callback()
409 try: 1hceafbdg
410 callback_help = typer_info.typer_instance.registered_callback.help 1hceafbdg
411 if not isinstance(callback_help, DefaultPlaceholder): 1hceafbdg
412 return inspect.cleandoc(callback_help or "") 1hceafbdg
413 except AttributeError: 1hceafbdg
414 pass 1hceafbdg
415 # Priority 3: Explicit value was set in sub_app = typer.Typer()
416 try: 1hceafbdg
417 instance_help = typer_info.typer_instance.info.help 1hceafbdg
418 if not isinstance(instance_help, DefaultPlaceholder): 1hceafbdg
419 return inspect.cleandoc(instance_help or "") 1hceafbdg
420 except AttributeError: 1hceafbdg
421 pass 1hceafbdg
422 # Priority 4: Implicit inference from callback docstring in app.add_typer()
423 if typer_info.callback: 1hceafbdg
424 doc = inspect.getdoc(typer_info.callback) 1hceafbdg
425 if doc: 1hceafbdg
426 return doc 1hceafbdg
427 # Priority 5: Implicit inference from callback docstring in @app.callback()
428 try: 1hceafbdg
429 callback = typer_info.typer_instance.registered_callback.callback 1hceafbdg
430 if not isinstance(callback, DefaultPlaceholder): 1hceafbdg
431 doc = inspect.getdoc(callback or "") 1hceafbdg
432 if doc: 1hceafbdg
433 return doc 1hceafbdg
434 except AttributeError: 1hceafbdg
435 pass 1hceafbdg
436 # Priority 6: Implicit inference from callback docstring in typer.Typer()
437 try: 1hceafbdg
438 instance_callback = typer_info.typer_instance.info.callback 1hceafbdg
439 if not isinstance(instance_callback, DefaultPlaceholder): 1hceafbdg
440 doc = inspect.getdoc(instance_callback) 1hceafbdg
441 if doc: 1hceafbdg
442 return doc 1hceafbdg
443 except AttributeError: 1hceafbdg
444 pass 1hceafbdg
445 # Value not set, use the default
446 return typer_info.help.value 1hceafbdg
449def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1hceafbdg
450 values: Dict[str, Any] = {} 1hceafbdg
451 for name, value in typer_info.__dict__.items(): 1hceafbdg
452 # Priority 1: Value was set in app.add_typer()
453 if not isinstance(value, DefaultPlaceholder): 1hceafbdg
454 values[name] = value 1hceafbdg
455 continue 1hceafbdg
456 # Priority 2: Value was set in @subapp.callback()
457 try: 1hceafbdg
458 callback_value = getattr( 1hceafbdg
459 typer_info.typer_instance.registered_callback, # type: ignore
460 name,
461 )
462 if not isinstance(callback_value, DefaultPlaceholder): 1hceafbdg
463 values[name] = callback_value 1hceafbdg
464 continue 1hceafbdg
465 except AttributeError: 1hceafbdg
466 pass 1hceafbdg
467 # Priority 3: Value set in subapp = typer.Typer()
468 try: 1hceafbdg
469 instance_value = getattr( 1hceafbdg
470 typer_info.typer_instance.info, # type: ignore
471 name,
472 )
473 if not isinstance(instance_value, DefaultPlaceholder): 1hceafbdg
474 values[name] = instance_value 1hceafbdg
475 continue 1hceafbdg
476 except AttributeError: 1hceafbdg
477 pass 1hceafbdg
478 # Value not set, use the default
479 values[name] = value.value 1hceafbdg
480 if values["name"] is None: 1hceafbdg
481 values["name"] = get_group_name(typer_info) 1hceafbdg
482 values["help"] = solve_typer_info_help(typer_info) 1hceafbdg
483 return TyperInfo(**values) 1hceafbdg
486def get_group_from_info( 1ceafbdg
487 group_info: TyperInfo,
488 *,
489 pretty_exceptions_short: bool,
490 rich_markup_mode: MarkupMode,
491) -> TyperGroup:
492 assert ( 1cabd
493 group_info.typer_instance
494 ), "A Typer instance is needed to generate a Click Group"
495 commands: Dict[str, click.Command] = {} 1hceafbdg
496 for command_info in group_info.typer_instance.registered_commands: 1hceafbdg
497 command = get_command_from_info( 1hceafbdg
498 command_info=command_info,
499 pretty_exceptions_short=pretty_exceptions_short,
500 rich_markup_mode=rich_markup_mode,
501 )
502 if command.name: 1hceafbdg
503 commands[command.name] = command 1hceafbdg
504 for sub_group_info in group_info.typer_instance.registered_groups: 1hceafbdg
505 sub_group = get_group_from_info( 1hceafbdg
506 sub_group_info,
507 pretty_exceptions_short=pretty_exceptions_short,
508 rich_markup_mode=rich_markup_mode,
509 )
510 if sub_group.name: 1hceafbdg
511 commands[sub_group.name] = sub_group 1hceafbdg
512 solved_info = solve_typer_info_defaults(group_info) 1hceafbdg
513 ( 1ceafbdg
514 params,
515 convertors,
516 context_param_name,
517 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback)
518 cls = solved_info.cls or TyperGroup 1hceafbdg
519 assert issubclass(cls, TyperGroup), f"{cls} should be a subclass of {TyperGroup}" 1hceafbdg
520 group = cls( 1hceafbdg
521 name=solved_info.name or "",
522 commands=commands,
523 invoke_without_command=solved_info.invoke_without_command,
524 no_args_is_help=solved_info.no_args_is_help,
525 subcommand_metavar=solved_info.subcommand_metavar,
526 chain=solved_info.chain,
527 result_callback=solved_info.result_callback,
528 context_settings=solved_info.context_settings,
529 callback=get_callback(
530 callback=solved_info.callback,
531 params=params,
532 convertors=convertors,
533 context_param_name=context_param_name,
534 pretty_exceptions_short=pretty_exceptions_short,
535 ),
536 params=params,
537 help=solved_info.help,
538 epilog=solved_info.epilog,
539 short_help=solved_info.short_help,
540 options_metavar=solved_info.options_metavar,
541 add_help_option=solved_info.add_help_option,
542 hidden=solved_info.hidden,
543 deprecated=solved_info.deprecated,
544 rich_markup_mode=rich_markup_mode,
545 # Rich settings
546 rich_help_panel=solved_info.rich_help_panel,
547 )
548 return group 1hceafbdg
551def get_command_name(name: str) -> str: 1hceafbdg
552 return name.lower().replace("_", "-") 1hceafbdg
555def get_params_convertors_ctx_param_name_from_function( 1ceafbdg
556 callback: Optional[Callable[..., Any]],
557) -> Tuple[List[Union[click.Argument, click.Option]], Dict[str, Any], Optional[str]]:
558 params = [] 1hceafbdg
559 convertors = {} 1hceafbdg
560 context_param_name = None 1hceafbdg
561 if callback: 1hceafbdg
562 parameters = get_params_from_function(callback) 1hceafbdg
563 for param_name, param in parameters.items(): 1hceafbdg
564 if lenient_issubclass(param.annotation, click.Context): 1hceafbdg
565 context_param_name = param_name 1hceafbdg
566 continue 1hceafbdg
567 click_param, convertor = get_click_param(param) 1hceafbdg
568 if convertor: 1hceafbdg
569 convertors[param_name] = convertor 1hceafbdg
570 params.append(click_param) 1hceafbdg
571 return params, convertors, context_param_name 1hceafbdg
574def get_command_from_info( 1ceafbdg
575 command_info: CommandInfo,
576 *,
577 pretty_exceptions_short: bool,
578 rich_markup_mode: MarkupMode,
579) -> click.Command:
580 assert command_info.callback, "A command must have a callback function" 1hceafbdg
581 name = command_info.name or get_command_name(command_info.callback.__name__) 1hceafbdg
582 use_help = command_info.help 1hceafbdg
583 if use_help is None: 1hceafbdg
584 use_help = inspect.getdoc(command_info.callback) 1hceafbdg
585 else:
586 use_help = inspect.cleandoc(use_help) 1hceafbdg
587 ( 1ceafbdg
588 params,
589 convertors,
590 context_param_name,
591 ) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
592 cls = command_info.cls or TyperCommand 1hceafbdg
593 command = cls( 1hceafbdg
594 name=name,
595 context_settings=command_info.context_settings,
596 callback=get_callback(
597 callback=command_info.callback,
598 params=params,
599 convertors=convertors,
600 context_param_name=context_param_name,
601 pretty_exceptions_short=pretty_exceptions_short,
602 ),
603 params=params, # type: ignore
604 help=use_help,
605 epilog=command_info.epilog,
606 short_help=command_info.short_help,
607 options_metavar=command_info.options_metavar,
608 add_help_option=command_info.add_help_option,
609 no_args_is_help=command_info.no_args_is_help,
610 hidden=command_info.hidden,
611 deprecated=command_info.deprecated,
612 rich_markup_mode=rich_markup_mode,
613 # Rich settings
614 rich_help_panel=command_info.rich_help_panel,
615 )
616 return command 1hceafbdg
619def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]: 1hceafbdg
620 convertor: Optional[Callable[[Any], Any]] = None 1hceafbdg
621 if lenient_issubclass(type_, Path): 1hceafbdg
622 convertor = param_path_convertor 1hceafbdg
623 if lenient_issubclass(type_, Enum): 1hceafbdg
624 convertor = generate_enum_convertor(type_) 1hceafbdg
625 return convertor 1hceafbdg
628def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: 1hceafbdg
629 if value is not None: 1hceafbdg
630 return Path(value) 1hceafbdg
631 return None 1hceafbdg
634def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]: 1hceafbdg
635 val_map = {str(val.value): val for val in enum} 1hceafbdg
637 def convertor(value: Any) -> Any: 1hceafbdg
638 if value is not None: 1hceafbdg
639 val = str(value) 1hceafbdg
640 if val in val_map: 1hceafbdg
641 key = val_map[val] 1hceafbdg
642 return enum(key) 1hceafbdg
644 return convertor 1hceafbdg
647def generate_list_convertor( 1ceafbdg
648 convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any]
649) -> Callable[[Sequence[Any]], Optional[List[Any]]]:
650 def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]: 1hceafbdg
651 if default_value is None and len(value) == 0: 1hceafbdg
652 return None 1hceafbdg
653 return [convertor(v) if convertor else v for v in value] 1hceafbdg
655 return internal_convertor 1hceafbdg
658def generate_tuple_convertor( 1ceafbdg
659 types: Sequence[Any],
660) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]:
661 convertors = [determine_type_convertor(type_) for type_ in types] 1hceafbdg
663 def internal_convertor( 1ceafbdg
664 param_args: Optional[Tuple[Any, ...]],
665 ) -> Optional[Tuple[Any, ...]]:
666 if param_args is None: 1hceafbdg
667 return None 1hceafbdg
668 return tuple( 1hceafbdg
669 convertor(arg) if convertor else arg
670 for (convertor, arg) in zip(convertors, param_args)
671 )
673 return internal_convertor 1hceafbdg
676def get_callback( 1ceafbdg
677 *,
678 callback: Optional[Callable[..., Any]] = None,
679 params: Sequence[click.Parameter] = [],
680 convertors: Optional[Dict[str, Callable[[str], Any]]] = None,
681 context_param_name: Optional[str] = None,
682 pretty_exceptions_short: bool,
683) -> Optional[Callable[..., Any]]:
684 use_convertors = convertors or {} 1hceafbdg
685 if not callback: 1hceafbdg
686 return None 1hceafbdg
687 parameters = get_params_from_function(callback) 1hceafbdg
688 use_params: Dict[str, Any] = {} 1hceafbdg
689 for param_name in parameters: 1hceafbdg
690 use_params[param_name] = None 1hceafbdg
691 for param in params: 1hceafbdg
692 if param.name: 1hceafbdg
693 use_params[param.name] = param.default 1hceafbdg
695 def wrapper(**kwargs: Any) -> Any: 1hceafbdg
696 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1hceafbdg
697 for k, v in kwargs.items(): 1hceafbdg
698 if k in use_convertors: 1hceafbdg
699 use_params[k] = use_convertors[k](v) 1hceafbdg
700 else:
701 use_params[k] = v 1hceafbdg
702 if context_param_name: 1hceafbdg
703 use_params[context_param_name] = click.get_current_context() 1hceafbdg
704 return callback(**use_params) 1hceafbdg
706 update_wrapper(wrapper, callback) 1hceafbdg
707 return wrapper 1hceafbdg
710def get_click_type( 1ceafbdg
711 *, annotation: Any, parameter_info: ParameterInfo
712) -> click.ParamType:
713 if parameter_info.click_type is not None: 1hceafbdg
714 return parameter_info.click_type 1hceafbdg
716 elif parameter_info.parser is not None: 1hceafbdg
717 return click.types.FuncParamType(parameter_info.parser) 1hceafbdg
719 elif annotation is str: 1hceafbdg
720 return click.STRING 1hceafbdg
721 elif annotation is int: 1hceafbdg
722 if parameter_info.min is not None or parameter_info.max is not None: 1hceafbdg
723 min_ = None 1hceafbdg
724 max_ = None 1hceafbdg
725 if parameter_info.min is not None: 1hceafbdg
726 min_ = int(parameter_info.min) 1hceafbdg
727 if parameter_info.max is not None: 1hceafbdg
728 max_ = int(parameter_info.max) 1hceafbdg
729 return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1hceafbdg
730 else:
731 return click.INT 1hceafbdg
732 elif annotation is float: 1hceafbdg
733 if parameter_info.min is not None or parameter_info.max is not None: 1hceafbdg
734 return click.FloatRange( 1hceafbdg
735 min=parameter_info.min,
736 max=parameter_info.max,
737 clamp=parameter_info.clamp,
738 )
739 else:
740 return click.FLOAT 1hceafbdg
741 elif annotation is bool: 1hceafbdg
742 return click.BOOL 1hceafbdg
743 elif annotation == UUID: 1hceafbdg
744 return click.UUID 1hceafbdg
745 elif annotation == datetime: 1hceafbdg
746 return click.DateTime(formats=parameter_info.formats) 1hceafbdg
747 elif ( 1ab
748 annotation == Path
749 or parameter_info.allow_dash
750 or parameter_info.path_type
751 or parameter_info.resolve_path
752 ):
753 return click.Path( 1hceafbdg
754 exists=parameter_info.exists,
755 file_okay=parameter_info.file_okay,
756 dir_okay=parameter_info.dir_okay,
757 writable=parameter_info.writable,
758 readable=parameter_info.readable,
759 resolve_path=parameter_info.resolve_path,
760 allow_dash=parameter_info.allow_dash,
761 path_type=parameter_info.path_type,
762 )
763 elif lenient_issubclass(annotation, FileTextWrite): 1hceafbdg
764 return click.File( 1hceafbdg
765 mode=parameter_info.mode or "w",
766 encoding=parameter_info.encoding,
767 errors=parameter_info.errors,
768 lazy=parameter_info.lazy,
769 atomic=parameter_info.atomic,
770 )
771 elif lenient_issubclass(annotation, FileText): 1hceafbdg
772 return click.File( 1hceafbdg
773 mode=parameter_info.mode or "r",
774 encoding=parameter_info.encoding,
775 errors=parameter_info.errors,
776 lazy=parameter_info.lazy,
777 atomic=parameter_info.atomic,
778 )
779 elif lenient_issubclass(annotation, FileBinaryRead): 1hceafbdg
780 return click.File( 1hceafbdg
781 mode=parameter_info.mode or "rb",
782 encoding=parameter_info.encoding,
783 errors=parameter_info.errors,
784 lazy=parameter_info.lazy,
785 atomic=parameter_info.atomic,
786 )
787 elif lenient_issubclass(annotation, FileBinaryWrite): 1hceafbdg
788 return click.File( 1hceafbdg
789 mode=parameter_info.mode or "wb",
790 encoding=parameter_info.encoding,
791 errors=parameter_info.errors,
792 lazy=parameter_info.lazy,
793 atomic=parameter_info.atomic,
794 )
795 elif lenient_issubclass(annotation, Enum): 1hceafbdg
796 return click.Choice( 1hceafbdg
797 [item.value for item in annotation],
798 case_sensitive=parameter_info.case_sensitive,
799 )
800 raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover
803def lenient_issubclass( 1ceafbdg
804 cls: Any, class_or_tuple: Union[AnyType, Tuple[AnyType, ...]]
805) -> bool:
806 return isinstance(cls, type) and issubclass(cls, class_or_tuple) 1hceafbdg
809def get_click_param( 1ceafbdg
810 param: ParamMeta,
811) -> Tuple[Union[click.Argument, click.Option], Any]:
812 # First, find out what will be:
813 # * ParamInfo (ArgumentInfo or OptionInfo)
814 # * default_value
815 # * required
816 default_value = None 1hceafbdg
817 required = False 1hceafbdg
818 if isinstance(param.default, ParameterInfo): 1hceafbdg
819 parameter_info = param.default 1hceafbdg
820 if parameter_info.default == Required: 1hceafbdg
821 required = True 1hceafbdg
822 else:
823 default_value = parameter_info.default 1hceafbdg
824 elif param.default == Required or param.default == param.empty: 1hceafbdg
825 required = True 1hceafbdg
826 parameter_info = ArgumentInfo() 1hceafbdg
827 else:
828 default_value = param.default 1hceafbdg
829 parameter_info = OptionInfo() 1hceafbdg
830 annotation: Any
831 if not param.annotation == param.empty: 1hceafbdg
832 annotation = param.annotation 1hceafbdg
833 else:
834 annotation = str 1hceafbdg
835 main_type = annotation 1hceafbdg
836 is_list = False 1hceafbdg
837 is_tuple = False 1hceafbdg
838 parameter_type: Any = None 1hceafbdg
839 is_flag = None 1hceafbdg
840 origin = get_origin(main_type) 1hceafbdg
842 if origin is not None: 1hceafbdg
843 # Handle SomeType | None and Optional[SomeType]
844 if is_union(origin): 1hceafbdg
845 types = [] 1hceafbdg
846 for type_ in get_args(main_type): 1hceafbdg
847 if type_ is NoneType: 1hceafbdg
848 continue 1hceafbdg
849 types.append(type_) 1hceafbdg
850 assert len(types) == 1, "Typer Currently doesn't support Union types" 1hceafbdg
851 main_type = types[0] 1hceafbdg
852 origin = get_origin(main_type) 1hceafbdg
853 # Handle Tuples and Lists
854 if lenient_issubclass(origin, List): 1hceafbdg
855 main_type = get_args(main_type)[0] 1hceafbdg
856 assert not get_origin( 1hceafbdg
857 main_type
858 ), "List types with complex sub-types are not currently supported"
859 is_list = True 1hceafbdg
860 elif lenient_issubclass(origin, Tuple): # type: ignore 1hceafbdg
861 types = [] 1hceafbdg
862 for type_ in get_args(main_type): 1hceafbdg
863 assert not get_origin( 1hceafbdg
864 type_
865 ), "Tuple types with complex sub-types are not currently supported"
866 types.append( 1hceafbdg
867 get_click_type(annotation=type_, parameter_info=parameter_info)
868 )
869 parameter_type = tuple(types) 1hceafbdg
870 is_tuple = True 1hceafbdg
871 if parameter_type is None: 1hceafbdg
872 parameter_type = get_click_type( 1hceafbdg
873 annotation=main_type, parameter_info=parameter_info
874 )
875 convertor = determine_type_convertor(main_type) 1hceafbdg
876 if is_list: 1hceafbdg
877 convertor = generate_list_convertor( 1hceafbdg
878 convertor=convertor, default_value=default_value
879 )
880 if is_tuple: 1hceafbdg
881 convertor = generate_tuple_convertor(get_args(main_type)) 1hceafbdg
882 if isinstance(parameter_info, OptionInfo): 1hceafbdg
883 if main_type is bool and parameter_info.is_flag is not False: 1hceafbdg
884 is_flag = True 1hceafbdg
885 # Click doesn't accept a flag of type bool, only None, and then it sets it
886 # to bool internally
887 parameter_type = None 1hceafbdg
888 default_option_name = get_command_name(param.name) 1hceafbdg
889 if is_flag: 1hceafbdg
890 default_option_declaration = ( 1ceafbdg
891 f"--{default_option_name}/--no-{default_option_name}"
892 )
893 else:
894 default_option_declaration = f"--{default_option_name}" 1hceafbdg
895 param_decls = [param.name] 1hceafbdg
896 if parameter_info.param_decls: 1hceafbdg
897 param_decls.extend(parameter_info.param_decls) 1hceafbdg
898 else:
899 param_decls.append(default_option_declaration) 1hceafbdg
900 return ( 1ceafbdg
901 TyperOption(
902 # Option
903 param_decls=param_decls,
904 show_default=parameter_info.show_default,
905 prompt=parameter_info.prompt,
906 confirmation_prompt=parameter_info.confirmation_prompt,
907 prompt_required=parameter_info.prompt_required,
908 hide_input=parameter_info.hide_input,
909 is_flag=is_flag,
910 flag_value=parameter_info.flag_value,
911 multiple=is_list,
912 count=parameter_info.count,
913 allow_from_autoenv=parameter_info.allow_from_autoenv,
914 type=parameter_type,
915 help=parameter_info.help,
916 hidden=parameter_info.hidden,
917 show_choices=parameter_info.show_choices,
918 show_envvar=parameter_info.show_envvar,
919 # Parameter
920 required=required,
921 default=default_value,
922 callback=get_param_callback(
923 callback=parameter_info.callback, convertor=convertor
924 ),
925 metavar=parameter_info.metavar,
926 expose_value=parameter_info.expose_value,
927 is_eager=parameter_info.is_eager,
928 envvar=parameter_info.envvar,
929 shell_complete=parameter_info.shell_complete,
930 autocompletion=get_param_completion(parameter_info.autocompletion),
931 # Rich settings
932 rich_help_panel=parameter_info.rich_help_panel,
933 ),
934 convertor,
935 )
936 elif isinstance(parameter_info, ArgumentInfo): 1hceafbdg
937 param_decls = [param.name] 1hceafbdg
938 nargs = None 1hceafbdg
939 if is_list: 1hceafbdg
940 nargs = -1 1hceafbdg
941 return ( 1ceafbdg
942 TyperArgument(
943 # Argument
944 param_decls=param_decls,
945 type=parameter_type,
946 required=required,
947 nargs=nargs,
948 # TyperArgument
949 show_default=parameter_info.show_default,
950 show_choices=parameter_info.show_choices,
951 show_envvar=parameter_info.show_envvar,
952 help=parameter_info.help,
953 hidden=parameter_info.hidden,
954 # Parameter
955 default=default_value,
956 callback=get_param_callback(
957 callback=parameter_info.callback, convertor=convertor
958 ),
959 metavar=parameter_info.metavar,
960 expose_value=parameter_info.expose_value,
961 is_eager=parameter_info.is_eager,
962 envvar=parameter_info.envvar,
963 shell_complete=parameter_info.shell_complete,
964 autocompletion=get_param_completion(parameter_info.autocompletion),
965 # Rich settings
966 rich_help_panel=parameter_info.rich_help_panel,
967 ),
968 convertor,
969 )
970 raise AssertionError("A click.Parameter should be returned") # pragma: no cover
973def get_param_callback( 1ceafbdg
974 *,
975 callback: Optional[Callable[..., Any]] = None,
976 convertor: Optional[Callable[..., Any]] = None,
977) -> Optional[Callable[..., Any]]:
978 if not callback: 1hceafbdg
979 return None 1hceafbdg
980 parameters = get_params_from_function(callback) 1hceafbdg
981 ctx_name = None 1hceafbdg
982 click_param_name = None 1hceafbdg
983 value_name = None 1hceafbdg
984 untyped_names: List[str] = [] 1hceafbdg
985 for param_name, param_sig in parameters.items(): 1hceafbdg
986 if lenient_issubclass(param_sig.annotation, click.Context): 1hceafbdg
987 ctx_name = param_name 1hceafbdg
988 elif lenient_issubclass(param_sig.annotation, click.Parameter): 1hceafbdg
989 click_param_name = param_name 1hceafbdg
990 else:
991 untyped_names.append(param_name) 1hceafbdg
992 # Extract value param name first
993 if untyped_names: 1hceafbdg
994 value_name = untyped_names.pop() 1hceafbdg
995 # If context and Click param were not typed (old/Click callback style) extract them
996 if untyped_names: 1hceafbdg
997 if ctx_name is None: 1hceafbdg
998 ctx_name = untyped_names.pop(0) 1hceafbdg
999 if click_param_name is None: 1hceafbdg
1000 if untyped_names: 1hceafbdg
1001 click_param_name = untyped_names.pop(0) 1hceafbdg
1002 if untyped_names: 1hceafbdg
1003 raise click.ClickException( 1hceafbdg
1004 "Too many CLI parameter callback function parameters"
1005 )
1007 def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: 1hceafbdg
1008 use_params: Dict[str, Any] = {} 1hceafbdg
1009 if ctx_name: 1hceafbdg
1010 use_params[ctx_name] = ctx 1hceafbdg
1011 if click_param_name: 1hceafbdg
1012 use_params[click_param_name] = param 1hceafbdg
1013 if value_name: 1hceafbdg
1014 if convertor: 1hceafbdg
1015 use_value = convertor(value) 1hceafbdg
1016 else:
1017 use_value = value 1hceafbdg
1018 use_params[value_name] = use_value 1hceafbdg
1019 return callback(**use_params) 1hceafbdg
1021 update_wrapper(wrapper, callback) 1hceafbdg
1022 return wrapper 1hceafbdg
1025def get_param_completion( 1ceafbdg
1026 callback: Optional[Callable[..., Any]] = None,
1027) -> Optional[Callable[..., Any]]:
1028 if not callback: 1hceafbdg
1029 return None 1hceafbdg
1030 parameters = get_params_from_function(callback) 1hceafbdg
1031 ctx_name = None 1hceafbdg
1032 args_name = None 1hceafbdg
1033 incomplete_name = None 1hceafbdg
1034 unassigned_params = list(parameters.values()) 1hceafbdg
1035 for param_sig in unassigned_params[:]: 1hceafbdg
1036 origin = get_origin(param_sig.annotation) 1hceafbdg
1037 if lenient_issubclass(param_sig.annotation, click.Context): 1hceafbdg
1038 ctx_name = param_sig.name 1hceafbdg
1039 unassigned_params.remove(param_sig) 1hceafbdg
1040 elif lenient_issubclass(origin, List): 1hceafbdg
1041 args_name = param_sig.name 1hceafbdg
1042 unassigned_params.remove(param_sig) 1hceafbdg
1043 elif lenient_issubclass(param_sig.annotation, str): 1hceafbdg
1044 incomplete_name = param_sig.name 1hceafbdg
1045 unassigned_params.remove(param_sig) 1hceafbdg
1046 # If there are still unassigned parameters (not typed), extract by name
1047 for param_sig in unassigned_params[:]: 1hceafbdg
1048 if ctx_name is None and param_sig.name == "ctx": 1hceafbdg
1049 ctx_name = param_sig.name 1hceafbdg
1050 unassigned_params.remove(param_sig) 1hceafbdg
1051 elif args_name is None and param_sig.name == "args": 1hceafbdg
1052 args_name = param_sig.name 1hceafbdg
1053 unassigned_params.remove(param_sig) 1hceafbdg
1054 elif incomplete_name is None and param_sig.name == "incomplete": 1hceafbdg
1055 incomplete_name = param_sig.name 1hceafbdg
1056 unassigned_params.remove(param_sig) 1hceafbdg
1057 # Extract value param name first
1058 if unassigned_params: 1hceafbdg
1059 show_params = " ".join([param.name for param in unassigned_params]) 1hceafbdg
1060 raise click.ClickException( 1hceafbdg
1061 f"Invalid autocompletion callback parameters: {show_params}"
1062 )
1064 def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any: 1hceafbdg
1065 use_params: Dict[str, Any] = {} 1hceafbdg
1066 if ctx_name: 1hceafbdg
1067 use_params[ctx_name] = ctx 1hceafbdg
1068 if args_name: 1hceafbdg
1069 use_params[args_name] = args 1hceafbdg
1070 if incomplete_name: 1hceafbdg
1071 use_params[incomplete_name] = incomplete 1hceafbdg
1072 return callback(**use_params) 1hceafbdg
1074 update_wrapper(wrapper, callback) 1hceafbdg
1075 return wrapper 1hceafbdg
1078def run(function: Callable[..., Any]) -> None: 1hceafbdg
1079 app = Typer(add_completion=False) 1hceafbdg
1080 app.command()(function) 1hceafbdg
1081 app() 1hceafbdg