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