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