Coverage for typer / main.py: 100%

497 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-02-09 12:36 +0000

1import inspect 1bdeafgch

2import os 1bdeafgch

3import platform 1bdeafgch

4import shutil 1bdeafgch

5import subprocess 1bdeafgch

6import sys 1bdeafgch

7import traceback 1bdeafgch

8from collections.abc import Sequence 1bdeafgch

9from datetime import datetime 1bdeafgch

10from enum import Enum 1bdeafgch

11from functools import update_wrapper 1bdeafgch

12from pathlib import Path 1bdeafgch

13from traceback import FrameSummary, StackSummary 1bdeafgch

14from types import TracebackType 1bdeafgch

15from typing import Any, Callable, Optional, Union 1bdeafgch

16from uuid import UUID 1bdeafgch

17 

18import click 1bdeafgch

19from typer._types import TyperChoice 1bdeafgch

20 

21from ._typing import get_args, get_origin, is_literal_type, is_union, literal_values 1bdeafgch

22from .completion import get_completion_inspect_parameters 1bdeafgch

23from .core import ( 1bdeafgch

24 DEFAULT_MARKUP_MODE, 

25 HAS_RICH, 

26 MarkupMode, 

27 TyperArgument, 

28 TyperCommand, 

29 TyperGroup, 

30 TyperOption, 

31) 

32from .models import ( 1bdeafgch

33 AnyType, 

34 ArgumentInfo, 

35 CommandFunctionType, 

36 CommandInfo, 

37 Default, 

38 DefaultPlaceholder, 

39 DeveloperExceptionConfig, 

40 FileBinaryRead, 

41 FileBinaryWrite, 

42 FileText, 

43 FileTextWrite, 

44 NoneType, 

45 OptionInfo, 

46 ParameterInfo, 

47 ParamMeta, 

48 Required, 

49 TyperInfo, 

50 TyperPath, 

51) 

52from .utils import get_params_from_function 1bdeafgch

53 

54_original_except_hook = sys.excepthook 1bdeafgch

55_typer_developer_exception_attr_name = "__typer_developer_exception__" 1bdeafgch

56 

57 

58def except_hook( 1bdeafgch

59 exc_type: type[BaseException], exc_value: BaseException, tb: Optional[TracebackType] 

60) -> None: 

61 exception_config: Union[DeveloperExceptionConfig, None] = getattr( 1bdeafgch

62 exc_value, _typer_developer_exception_attr_name, None 

63 ) 

64 standard_traceback = os.getenv( 1bdeafgch

65 "TYPER_STANDARD_TRACEBACK", os.getenv("_TYPER_STANDARD_TRACEBACK") 

66 ) 

67 if ( 1bac

68 standard_traceback 

69 or not exception_config 

70 or not exception_config.pretty_exceptions_enable 

71 ): 

72 _original_except_hook(exc_type, exc_value, tb) 1bdeafgch

73 return 1bdeafgch

74 typer_path = os.path.dirname(__file__) 1bdeafgch

75 click_path = os.path.dirname(click.__file__) 1bdeafgch

76 internal_dir_names = [typer_path, click_path] 1bdeafgch

77 exc = exc_value 1bdeafgch

78 if HAS_RICH: 1bdeafgch

79 from . import rich_utils 1bdeafgch

80 

81 rich_tb = rich_utils.get_traceback(exc, exception_config, internal_dir_names) 1bdeafgch

82 console_stderr = rich_utils._get_rich_console(stderr=True) 1bdeafgch

83 console_stderr.print(rich_tb) 1bdeafgch

84 return 1bdeafgch

85 tb_exc = traceback.TracebackException.from_exception(exc) 1bdeafgch

86 stack: list[FrameSummary] = [] 1bdeafgch

87 for frame in tb_exc.stack: 1bdeafgch

88 if any(frame.filename.startswith(path) for path in internal_dir_names): 1bdeafgch

89 if not exception_config.pretty_exceptions_short: 1bdeafgch

90 # Hide the line for internal libraries, Typer and Click 

91 stack.append( 1bdeafgch

92 traceback.FrameSummary( 

93 filename=frame.filename, 

94 lineno=frame.lineno, 

95 name=frame.name, 

96 line="", 

97 ) 

98 ) 

99 else: 

100 stack.append(frame) 1bdeafgch

101 # Type ignore ref: https://github.com/python/typeshed/pull/8244 

102 final_stack_summary = StackSummary.from_list(stack) 1bdeafgch

103 tb_exc.stack = final_stack_summary 1bdeafgch

104 for line in tb_exc.format(): 1bdeafgch

105 print(line, file=sys.stderr) 1bdeafgch

106 return 1bdeafgch

107 

108 

109def get_install_completion_arguments() -> tuple[click.Parameter, click.Parameter]: 1bdeafgch

110 install_param, show_param = get_completion_inspect_parameters() 1bdeafgch

111 click_install_param, _ = get_click_param(install_param) 1bdeafgch

112 click_show_param, _ = get_click_param(show_param) 1bdeafgch

113 return click_install_param, click_show_param 1bdeafgch

114 

115 

116class Typer: 1bdeafgch

117 def __init__( 1bdeafgch

118 self, 

119 *, 

120 name: Optional[str] = Default(None), 

121 cls: Optional[type[TyperGroup]] = Default(None), 

122 invoke_without_command: bool = Default(False), 

123 no_args_is_help: bool = Default(False), 

124 subcommand_metavar: Optional[str] = Default(None), 

125 chain: bool = Default(False), 

126 result_callback: Optional[Callable[..., Any]] = Default(None), 

127 # Command 

128 context_settings: Optional[dict[Any, Any]] = Default(None), 

129 callback: Optional[Callable[..., Any]] = Default(None), 

130 help: Optional[str] = Default(None), 

131 epilog: Optional[str] = Default(None), 

132 short_help: Optional[str] = Default(None), 

133 options_metavar: str = Default("[OPTIONS]"), 

134 add_help_option: bool = Default(True), 

135 hidden: bool = Default(False), 

136 deprecated: bool = Default(False), 

137 add_completion: bool = True, 

138 # Rich settings 

139 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

140 rich_help_panel: Union[str, None] = Default(None), 

141 suggest_commands: bool = True, 

142 pretty_exceptions_enable: bool = True, 

143 pretty_exceptions_show_locals: bool = True, 

144 pretty_exceptions_short: bool = True, 

145 ): 

146 self._add_completion = add_completion 1bdeafgch

147 self.rich_markup_mode: MarkupMode = rich_markup_mode 1bdeafgch

148 self.rich_help_panel = rich_help_panel 1bdeafgch

149 self.suggest_commands = suggest_commands 1bdeafgch

150 self.pretty_exceptions_enable = pretty_exceptions_enable 1bdeafgch

151 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1bdeafgch

152 self.pretty_exceptions_short = pretty_exceptions_short 1bdeafgch

153 self.info = TyperInfo( 1bdeafgch

154 name=name, 

155 cls=cls, 

156 invoke_without_command=invoke_without_command, 

157 no_args_is_help=no_args_is_help, 

158 subcommand_metavar=subcommand_metavar, 

159 chain=chain, 

160 result_callback=result_callback, 

161 context_settings=context_settings, 

162 callback=callback, 

163 help=help, 

164 epilog=epilog, 

165 short_help=short_help, 

166 options_metavar=options_metavar, 

167 add_help_option=add_help_option, 

168 hidden=hidden, 

169 deprecated=deprecated, 

170 ) 

171 self.registered_groups: list[TyperInfo] = [] 1bdeafgch

172 self.registered_commands: list[CommandInfo] = [] 1bdeafgch

173 self.registered_callback: Optional[TyperInfo] = None 1bdeafgch

174 

175 def callback( 1bdeafgch

176 self, 

177 *, 

178 cls: Optional[type[TyperGroup]] = Default(None), 

179 invoke_without_command: bool = Default(False), 

180 no_args_is_help: bool = Default(False), 

181 subcommand_metavar: Optional[str] = Default(None), 

182 chain: bool = Default(False), 

183 result_callback: Optional[Callable[..., Any]] = Default(None), 

184 # Command 

185 context_settings: Optional[dict[Any, Any]] = Default(None), 

186 help: Optional[str] = Default(None), 

187 epilog: Optional[str] = Default(None), 

188 short_help: Optional[str] = Default(None), 

189 options_metavar: Optional[str] = Default(None), 

190 add_help_option: bool = Default(True), 

191 hidden: bool = Default(False), 

192 deprecated: bool = Default(False), 

193 # Rich settings 

194 rich_help_panel: Union[str, None] = Default(None), 

195 ) -> Callable[[CommandFunctionType], CommandFunctionType]: 

196 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1bdeafgch

197 self.registered_callback = TyperInfo( 1bdeafgch

198 cls=cls, 

199 invoke_without_command=invoke_without_command, 

200 no_args_is_help=no_args_is_help, 

201 subcommand_metavar=subcommand_metavar, 

202 chain=chain, 

203 result_callback=result_callback, 

204 context_settings=context_settings, 

205 callback=f, 

206 help=help, 

207 epilog=epilog, 

208 short_help=short_help, 

209 options_metavar=( 

210 options_metavar or self._info_val_str("options_metavar") 

211 ), 

212 add_help_option=add_help_option, 

213 hidden=hidden, 

214 deprecated=deprecated, 

215 rich_help_panel=rich_help_panel, 

216 ) 

217 return f 1bdeafgch

218 

219 return decorator 1bdeafgch

220 

221 def command( 1bdeafgch

222 self, 

223 name: Optional[str] = None, 

224 *, 

225 cls: Optional[type[TyperCommand]] = None, 

226 context_settings: Optional[dict[Any, Any]] = None, 

227 help: Optional[str] = None, 

228 epilog: Optional[str] = None, 

229 short_help: Optional[str] = None, 

230 options_metavar: Optional[str] = None, 

231 add_help_option: bool = True, 

232 no_args_is_help: bool = False, 

233 hidden: bool = False, 

234 deprecated: bool = False, 

235 # Rich settings 

236 rich_help_panel: Union[str, None] = Default(None), 

237 ) -> Callable[[CommandFunctionType], CommandFunctionType]: 

238 if cls is None: 1bdeafgch

239 cls = TyperCommand 1bdeafgch

240 

241 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1bdeafgch

242 self.registered_commands.append( 1bdeafgch

243 CommandInfo( 

244 name=name, 

245 cls=cls, 

246 context_settings=context_settings, 

247 callback=f, 

248 help=help, 

249 epilog=epilog, 

250 short_help=short_help, 

251 options_metavar=( 

252 options_metavar or self._info_val_str("options_metavar") 

253 ), 

254 add_help_option=add_help_option, 

255 no_args_is_help=no_args_is_help, 

256 hidden=hidden, 

257 deprecated=deprecated, 

258 # Rich settings 

259 rich_help_panel=rich_help_panel, 

260 ) 

261 ) 

262 return f 1bdeafgch

263 

264 return decorator 1bdeafgch

265 

266 def add_typer( 1bdeafgch

267 self, 

268 typer_instance: "Typer", 

269 *, 

270 name: Optional[str] = Default(None), 

271 cls: Optional[type[TyperGroup]] = Default(None), 

272 invoke_without_command: bool = Default(False), 

273 no_args_is_help: bool = Default(False), 

274 subcommand_metavar: Optional[str] = Default(None), 

275 chain: bool = Default(False), 

276 result_callback: Optional[Callable[..., Any]] = Default(None), 

277 # Command 

278 context_settings: Optional[dict[Any, Any]] = Default(None), 

279 callback: Optional[Callable[..., Any]] = Default(None), 

280 help: Optional[str] = Default(None), 

281 epilog: Optional[str] = Default(None), 

282 short_help: Optional[str] = Default(None), 

283 options_metavar: Optional[str] = Default(None), 

284 add_help_option: bool = Default(True), 

285 hidden: bool = Default(False), 

286 deprecated: bool = Default(False), 

287 # Rich settings 

288 rich_help_panel: Union[str, None] = Default(None), 

289 ) -> None: 

290 self.registered_groups.append( 1bdeafgch

291 TyperInfo( 

292 typer_instance, 

293 name=name, 

294 cls=cls, 

295 invoke_without_command=invoke_without_command, 

296 no_args_is_help=no_args_is_help, 

297 subcommand_metavar=subcommand_metavar, 

298 chain=chain, 

299 result_callback=result_callback, 

300 context_settings=context_settings, 

301 callback=callback, 

302 help=help, 

303 epilog=epilog, 

304 short_help=short_help, 

305 options_metavar=( 

306 options_metavar or self._info_val_str("options_metavar") 

307 ), 

308 add_help_option=add_help_option, 

309 hidden=hidden, 

310 deprecated=deprecated, 

311 rich_help_panel=rich_help_panel, 

312 ) 

313 ) 

314 

315 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1bdeafgch

316 if sys.excepthook != except_hook: 1bdeafgch

317 sys.excepthook = except_hook 1bdeafgch

318 try: 1bdeafgch

319 return get_command(self)(*args, **kwargs) 1bdeafgch

320 except Exception as e: 1bdeafgch

321 # Set a custom attribute to tell the hook to show nice exceptions for user 

322 # code. An alternative/first implementation was a custom exception with 

323 # raise custom_exc from e 

324 # but that means the last error shown is the custom exception, not the 

325 # actual error. This trick improves developer experience by showing the 

326 # actual error last. 

327 setattr( 1bdeafgch

328 e, 

329 _typer_developer_exception_attr_name, 

330 DeveloperExceptionConfig( 

331 pretty_exceptions_enable=self.pretty_exceptions_enable, 

332 pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, 

333 pretty_exceptions_short=self.pretty_exceptions_short, 

334 ), 

335 ) 

336 raise e 1bdeafgch

337 

338 def _info_val_str(self, name: str) -> str: 1bdeafgch

339 val = getattr(self.info, name) 1bdeafgch

340 val_str = val.value if isinstance(val, DefaultPlaceholder) else val 1bdeafgch

341 assert isinstance(val_str, str) 1bdeafgch

342 return val_str 1bdeafgch

343 

344 

345def get_group(typer_instance: Typer) -> TyperGroup: 1bdeafgch

346 group = get_group_from_info( 1bdeafgch

347 TyperInfo(typer_instance), 

348 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

349 rich_markup_mode=typer_instance.rich_markup_mode, 

350 suggest_commands=typer_instance.suggest_commands, 

351 ) 

352 return group 1bdeafgch

353 

354 

355def get_command(typer_instance: Typer) -> click.Command: 1bdeafgch

356 if typer_instance._add_completion: 1bdeafgch

357 click_install_param, click_show_param = get_install_completion_arguments() 1bdeafgch

358 if ( 1bac

359 typer_instance.registered_callback 

360 or typer_instance.info.callback 

361 or typer_instance.registered_groups 

362 or len(typer_instance.registered_commands) > 1 

363 ): 

364 # Create a Group 

365 click_command: click.Command = get_group(typer_instance) 1bdeafgch

366 if typer_instance._add_completion: 1bdeafgch

367 click_command.params.append(click_install_param) 1bdeafgch

368 click_command.params.append(click_show_param) 1bdeafgch

369 return click_command 1bdeafgch

370 elif len(typer_instance.registered_commands) == 1: 1bdeafgch

371 # Create a single Command 

372 single_command = typer_instance.registered_commands[0] 1bdeafgch

373 

374 if not single_command.context_settings and not isinstance( 1bdeafgch

375 typer_instance.info.context_settings, DefaultPlaceholder 

376 ): 

377 single_command.context_settings = typer_instance.info.context_settings 1bdeafgch

378 

379 click_command = get_command_from_info( 1bdeafgch

380 single_command, 

381 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

382 rich_markup_mode=typer_instance.rich_markup_mode, 

383 ) 

384 if typer_instance._add_completion: 1bdeafgch

385 click_command.params.append(click_install_param) 1bdeafgch

386 click_command.params.append(click_show_param) 1bdeafgch

387 return click_command 1bdeafgch

388 raise RuntimeError( 

389 "Could not get a command for this Typer instance" 

390 ) # pragma: no cover 

391 

392 

393def solve_typer_info_help(typer_info: TyperInfo) -> str: 1bdeafgch

394 # Priority 1: Explicit value was set in app.add_typer() 

395 if not isinstance(typer_info.help, DefaultPlaceholder): 1bdeafgch

396 return inspect.cleandoc(typer_info.help or "") 1bdeafgch

397 # Priority 2: Explicit value was set in sub_app.callback() 

398 try: 1bdeafgch

399 callback_help = typer_info.typer_instance.registered_callback.help 1bdeafgch

400 if not isinstance(callback_help, DefaultPlaceholder): 1bdeafgch

401 return inspect.cleandoc(callback_help or "") 1bdeafgch

402 except AttributeError: 1bdeafgch

403 pass 1bdeafgch

404 # Priority 3: Explicit value was set in sub_app = typer.Typer() 

405 try: 1bdeafgch

406 instance_help = typer_info.typer_instance.info.help 1bdeafgch

407 if not isinstance(instance_help, DefaultPlaceholder): 1bdeafgch

408 return inspect.cleandoc(instance_help or "") 1bdeafgch

409 except AttributeError: 1bdeafgch

410 pass 1bdeafgch

411 # Priority 4: Implicit inference from callback docstring in app.add_typer() 

412 if typer_info.callback: 1bdeafgch

413 doc = inspect.getdoc(typer_info.callback) 1bdeafgch

414 if doc: 1bdeafgch

415 return doc 1bdeafgch

416 # Priority 5: Implicit inference from callback docstring in @app.callback() 

417 try: 1bdeafgch

418 callback = typer_info.typer_instance.registered_callback.callback 1bdeafgch

419 if not isinstance(callback, DefaultPlaceholder): 1bdeafgch

420 doc = inspect.getdoc(callback or "") 1bdeafgch

421 if doc: 1bdeafgch

422 return doc 1bdeafgch

423 except AttributeError: 1bdeafgch

424 pass 1bdeafgch

425 # Priority 6: Implicit inference from callback docstring in typer.Typer() 

426 try: 1bdeafgch

427 instance_callback = typer_info.typer_instance.info.callback 1bdeafgch

428 if not isinstance(instance_callback, DefaultPlaceholder): 1bdeafgch

429 doc = inspect.getdoc(instance_callback) 1bdeafgch

430 if doc: 1bdeafgch

431 return doc 1bdeafgch

432 except AttributeError: 1bdeafgch

433 pass 1bdeafgch

434 # Value not set, use the default 

435 return typer_info.help.value 1bdeafgch

436 

437 

438def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1bdeafgch

439 values: dict[str, Any] = {} 1bdeafgch

440 for name, value in typer_info.__dict__.items(): 1bdeafgch

441 # Priority 1: Value was set in app.add_typer() 

442 if not isinstance(value, DefaultPlaceholder): 1bdeafgch

443 values[name] = value 1bdeafgch

444 continue 1bdeafgch

445 # Priority 2: Value was set in @subapp.callback() 

446 try: 1bdeafgch

447 callback_value = getattr( 1bdeafgch

448 typer_info.typer_instance.registered_callback, # type: ignore 

449 name, 

450 ) 

451 if not isinstance(callback_value, DefaultPlaceholder): 1bdeafgch

452 values[name] = callback_value 1bdeafgch

453 continue 1bdeafgch

454 except AttributeError: 1bdeafgch

455 pass 1bdeafgch

456 # Priority 3: Value set in subapp = typer.Typer() 

457 try: 1bdeafgch

458 instance_value = getattr( 1bdeafgch

459 typer_info.typer_instance.info, # type: ignore 

460 name, 

461 ) 

462 if not isinstance(instance_value, DefaultPlaceholder): 1bdeafgch

463 values[name] = instance_value 1bdeafgch

464 continue 1bdeafgch

465 except AttributeError: 1bdeafgch

466 pass 1bdeafgch

467 # Value not set, use the default 

468 values[name] = value.value 1bdeafgch

469 values["help"] = solve_typer_info_help(typer_info) 1bdeafgch

470 return TyperInfo(**values) 1bdeafgch

471 

472 

473def get_group_from_info( 1bdeafgch

474 group_info: TyperInfo, 

475 *, 

476 pretty_exceptions_short: bool, 

477 suggest_commands: bool, 

478 rich_markup_mode: MarkupMode, 

479) -> TyperGroup: 

480 assert group_info.typer_instance, ( 1bdeafgch

481 "A Typer instance is needed to generate a Click Group" 

482 ) 

483 commands: dict[str, click.Command] = {} 1bdeafgch

484 for command_info in group_info.typer_instance.registered_commands: 1bdeafgch

485 command = get_command_from_info( 1bdeafgch

486 command_info=command_info, 

487 pretty_exceptions_short=pretty_exceptions_short, 

488 rich_markup_mode=rich_markup_mode, 

489 ) 

490 if command.name: 1bdeafgch

491 commands[command.name] = command 1bdeafgch

492 for sub_group_info in group_info.typer_instance.registered_groups: 1bdeafgch

493 sub_group = get_group_from_info( 1bdeafgch

494 sub_group_info, 

495 pretty_exceptions_short=pretty_exceptions_short, 

496 rich_markup_mode=rich_markup_mode, 

497 suggest_commands=suggest_commands, 

498 ) 

499 if sub_group.name: 1bdeafgch

500 commands[sub_group.name] = sub_group 1bdeafgch

501 else: 

502 if sub_group.callback: 1bdeafgch

503 import warnings 1bdeafgch

504 

505 warnings.warn( 1bdeafgch

506 "The 'callback' parameter is not supported by Typer when using `add_typer` without a name", 

507 stacklevel=5, 

508 ) 

509 for sub_command_name, sub_command in sub_group.commands.items(): 1bdeafgch

510 commands[sub_command_name] = sub_command 1bdeafgch

511 solved_info = solve_typer_info_defaults(group_info) 1bdeafgch

512 ( 1bdeafgch

513 params, 

514 convertors, 

515 context_param_name, 

516 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) 

517 cls = solved_info.cls or TyperGroup 1bdeafgch

518 assert issubclass(cls, TyperGroup), f"{cls} should be a subclass of {TyperGroup}" 1bdeafgch

519 group = cls( 1bdeafgch

520 name=solved_info.name or "", 

521 commands=commands, 

522 invoke_without_command=solved_info.invoke_without_command, 

523 no_args_is_help=solved_info.no_args_is_help, 

524 subcommand_metavar=solved_info.subcommand_metavar, 

525 chain=solved_info.chain, 

526 result_callback=solved_info.result_callback, 

527 context_settings=solved_info.context_settings, 

528 callback=get_callback( 

529 callback=solved_info.callback, 

530 params=params, 

531 convertors=convertors, 

532 context_param_name=context_param_name, 

533 pretty_exceptions_short=pretty_exceptions_short, 

534 ), 

535 params=params, 

536 help=solved_info.help, 

537 epilog=solved_info.epilog, 

538 short_help=solved_info.short_help, 

539 options_metavar=solved_info.options_metavar, 

540 add_help_option=solved_info.add_help_option, 

541 hidden=solved_info.hidden, 

542 deprecated=solved_info.deprecated, 

543 rich_markup_mode=rich_markup_mode, 

544 # Rich settings 

545 rich_help_panel=solved_info.rich_help_panel, 

546 suggest_commands=suggest_commands, 

547 ) 

548 return group 1bdeafgch

549 

550 

551def get_command_name(name: str) -> str: 1bdeafgch

552 return name.lower().replace("_", "-") 1bdeafgch

553 

554 

555def get_params_convertors_ctx_param_name_from_function( 1bdeafgch

556 callback: Optional[Callable[..., Any]], 

557) -> tuple[list[Union[click.Argument, click.Option]], dict[str, Any], Optional[str]]: 

558 params = [] 1bdeafgch

559 convertors = {} 1bdeafgch

560 context_param_name = None 1bdeafgch

561 if callback: 1bdeafgch

562 parameters = get_params_from_function(callback) 1bdeafgch

563 for param_name, param in parameters.items(): 1bdeafgch

564 if lenient_issubclass(param.annotation, click.Context): 1bdeafgch

565 context_param_name = param_name 1bdeafgch

566 continue 1bdeafgch

567 click_param, convertor = get_click_param(param) 1bdeafgch

568 if convertor: 1bdeafgch

569 convertors[param_name] = convertor 1bdeafgch

570 params.append(click_param) 1bdeafgch

571 return params, convertors, context_param_name 1bdeafgch

572 

573 

574def get_command_from_info( 1bdeafgch

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" 1bdeafgch

581 name = command_info.name or get_command_name(command_info.callback.__name__) 1bdeafgch

582 use_help = command_info.help 1bdeafgch

583 if use_help is None: 1bdeafgch

584 use_help = inspect.getdoc(command_info.callback) 1bdeafgch

585 else: 

586 use_help = inspect.cleandoc(use_help) 1bdeafgch

587 ( 1bdeafgch

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 1bdeafgch

593 command = cls( 1bdeafgch

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 1bdeafgch

617 

618 

619def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]: 1bdeafgch

620 convertor: Optional[Callable[[Any], Any]] = None 1bdeafgch

621 if lenient_issubclass(type_, Path): 1bdeafgch

622 convertor = param_path_convertor 1bdeafgch

623 if lenient_issubclass(type_, Enum): 1bdeafgch

624 convertor = generate_enum_convertor(type_) 1bdeafgch

625 return convertor 1bdeafgch

626 

627 

628def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: 1bdeafgch

629 if value is not None: 1bdeafgch

630 # allow returning any subclass of Path created by an annotated parser without converting 

631 # it back to a Path 

632 return value if isinstance(value, Path) else Path(value) 1bdeafgch

633 return None 1bdeafgch

634 

635 

636def generate_enum_convertor(enum: type[Enum]) -> Callable[[Any], Any]: 1bdeafgch

637 val_map = {str(val.value): val for val in enum} 1bdeafgch

638 

639 def convertor(value: Any) -> Any: 1bdeafgch

640 if value is not None: 1bdeafgch

641 val = str(value) 1bdeafgch

642 if val in val_map: 1bdeafgch

643 key = val_map[val] 1bdeafgch

644 return enum(key) 1bdeafgch

645 

646 return convertor 1bdeafgch

647 

648 

649def generate_list_convertor( 1bdeafgch

650 convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any] 

651) -> Callable[[Optional[Sequence[Any]]], Optional[list[Any]]]: 

652 def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[list[Any]]: 1bdeafgch

653 if (value is None) or (default_value is None and len(value) == 0): 1bdeafgch

654 return None 1bdeafgch

655 return [convertor(v) if convertor else v for v in value] 1bdeafgch

656 

657 return internal_convertor 1bdeafgch

658 

659 

660def generate_tuple_convertor( 1bdeafgch

661 types: Sequence[Any], 

662) -> Callable[[Optional[tuple[Any, ...]]], Optional[tuple[Any, ...]]]: 

663 convertors = [determine_type_convertor(type_) for type_ in types] 1bdeafgch

664 

665 def internal_convertor( 1bdeafgch

666 param_args: Optional[tuple[Any, ...]], 

667 ) -> Optional[tuple[Any, ...]]: 

668 if param_args is None: 1bdeafgch

669 return None 1bdeafgch

670 return tuple( 1bdeafgch

671 convertor(arg) if convertor else arg 

672 for (convertor, arg) in zip(convertors, param_args) 

673 ) 

674 

675 return internal_convertor 1bdeafgch

676 

677 

678def get_callback( 1bdeafgch

679 *, 

680 callback: Optional[Callable[..., Any]] = None, 

681 params: Sequence[click.Parameter] = [], 

682 convertors: Optional[dict[str, Callable[[str], Any]]] = None, 

683 context_param_name: Optional[str] = None, 

684 pretty_exceptions_short: bool, 

685) -> Optional[Callable[..., Any]]: 

686 use_convertors = convertors or {} 1bdeafgch

687 if not callback: 1bdeafgch

688 return None 1bdeafgch

689 parameters = get_params_from_function(callback) 1bdeafgch

690 use_params: dict[str, Any] = {} 1bdeafgch

691 for param_name in parameters: 1bdeafgch

692 use_params[param_name] = None 1bdeafgch

693 for param in params: 1bdeafgch

694 if param.name: 1bdeafgch

695 use_params[param.name] = param.default 1bdeafgch

696 

697 def wrapper(**kwargs: Any) -> Any: 1bdeafgch

698 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1bdeafgch

699 for k, v in kwargs.items(): 1bdeafgch

700 if k in use_convertors: 1bdeafgch

701 use_params[k] = use_convertors[k](v) 1bdeafgch

702 else: 

703 use_params[k] = v 1bdeafgch

704 if context_param_name: 1bdeafgch

705 use_params[context_param_name] = click.get_current_context() 1bdeafgch

706 return callback(**use_params) 1bdeafgch

707 

708 update_wrapper(wrapper, callback) 1bdeafgch

709 return wrapper 1bdeafgch

710 

711 

712def get_click_type( 1bdeafgch

713 *, annotation: Any, parameter_info: ParameterInfo 

714) -> click.ParamType: 

715 if parameter_info.click_type is not None: 1bdeafgch

716 return parameter_info.click_type 1bdeafgch

717 

718 elif parameter_info.parser is not None: 1bdeafgch

719 return click.types.FuncParamType(parameter_info.parser) 1bdeafgch

720 

721 elif annotation is str: 1bdeafgch

722 return click.STRING 1bdeafgch

723 elif annotation is int: 1bdeafgch

724 if parameter_info.min is not None or parameter_info.max is not None: 1bdeafgch

725 min_ = None 1bdeafgch

726 max_ = None 1bdeafgch

727 if parameter_info.min is not None: 1bdeafgch

728 min_ = int(parameter_info.min) 1bdeafgch

729 if parameter_info.max is not None: 1bdeafgch

730 max_ = int(parameter_info.max) 1bdeafgch

731 return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1bdeafgch

732 else: 

733 return click.INT 1bdeafgch

734 elif annotation is float: 1bdeafgch

735 if parameter_info.min is not None or parameter_info.max is not None: 1bdeafgch

736 return click.FloatRange( 1bdeafgch

737 min=parameter_info.min, 

738 max=parameter_info.max, 

739 clamp=parameter_info.clamp, 

740 ) 

741 else: 

742 return click.FLOAT 1bdeafgch

743 elif annotation is bool: 1bdeafgch

744 return click.BOOL 1bdeafgch

745 elif annotation == UUID: 1bdeafgch

746 return click.UUID 1bdeafgch

747 elif annotation == datetime: 1bdeafgch

748 return click.DateTime(formats=parameter_info.formats) 1bdeafgch

749 elif ( 1a

750 annotation == Path 

751 or parameter_info.allow_dash 

752 or parameter_info.path_type 

753 or parameter_info.resolve_path 

754 ): 

755 return TyperPath( 1bdeafgch

756 exists=parameter_info.exists, 

757 file_okay=parameter_info.file_okay, 

758 dir_okay=parameter_info.dir_okay, 

759 writable=parameter_info.writable, 

760 readable=parameter_info.readable, 

761 resolve_path=parameter_info.resolve_path, 

762 allow_dash=parameter_info.allow_dash, 

763 path_type=parameter_info.path_type, 

764 ) 

765 elif lenient_issubclass(annotation, FileTextWrite): 1bdeafgch

766 return click.File( 1bdeafgch

767 mode=parameter_info.mode or "w", 

768 encoding=parameter_info.encoding, 

769 errors=parameter_info.errors, 

770 lazy=parameter_info.lazy, 

771 atomic=parameter_info.atomic, 

772 ) 

773 elif lenient_issubclass(annotation, FileText): 1bdeafgch

774 return click.File( 1bdeafgch

775 mode=parameter_info.mode or "r", 

776 encoding=parameter_info.encoding, 

777 errors=parameter_info.errors, 

778 lazy=parameter_info.lazy, 

779 atomic=parameter_info.atomic, 

780 ) 

781 elif lenient_issubclass(annotation, FileBinaryRead): 1bdeafgch

782 return click.File( 1bdeafgch

783 mode=parameter_info.mode or "rb", 

784 encoding=parameter_info.encoding, 

785 errors=parameter_info.errors, 

786 lazy=parameter_info.lazy, 

787 atomic=parameter_info.atomic, 

788 ) 

789 elif lenient_issubclass(annotation, FileBinaryWrite): 1bdeafgch

790 return click.File( 1bdeafgch

791 mode=parameter_info.mode or "wb", 

792 encoding=parameter_info.encoding, 

793 errors=parameter_info.errors, 

794 lazy=parameter_info.lazy, 

795 atomic=parameter_info.atomic, 

796 ) 

797 elif lenient_issubclass(annotation, Enum): 1bdeafgch

798 # The custom TyperChoice is only needed for Click < 8.2.0, to parse the 

799 # command line values matching them to the enum values. Click 8.2.0 added 

800 # support for enum values but reading enum names. 

801 # Passing here the list of enum values (instead of just the enum) accounts for 

802 # Click < 8.2.0. 

803 return TyperChoice( 1bdeafgch

804 [item.value for item in annotation], 

805 case_sensitive=parameter_info.case_sensitive, 

806 ) 

807 elif is_literal_type(annotation): 1bdeafgch

808 return click.Choice( 1bdeafgch

809 literal_values(annotation), 

810 case_sensitive=parameter_info.case_sensitive, 

811 ) 

812 raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover 

813 

814 

815def lenient_issubclass( 1bdeafgch

816 cls: Any, class_or_tuple: Union[AnyType, tuple[AnyType, ...]] 

817) -> bool: 

818 return isinstance(cls, type) and issubclass(cls, class_or_tuple) 1bdeafgch

819 

820 

821def get_click_param( 1bdeafgch

822 param: ParamMeta, 

823) -> tuple[Union[click.Argument, click.Option], Any]: 

824 # First, find out what will be: 

825 # * ParamInfo (ArgumentInfo or OptionInfo) 

826 # * default_value 

827 # * required 

828 default_value = None 1bdeafgch

829 required = False 1bdeafgch

830 if isinstance(param.default, ParameterInfo): 1bdeafgch

831 parameter_info = param.default 1bdeafgch

832 if parameter_info.default == Required: 1bdeafgch

833 required = True 1bdeafgch

834 else: 

835 default_value = parameter_info.default 1bdeafgch

836 elif param.default == Required or param.default is param.empty: 1bdeafgch

837 required = True 1bdeafgch

838 parameter_info = ArgumentInfo() 1bdeafgch

839 else: 

840 default_value = param.default 1bdeafgch

841 parameter_info = OptionInfo() 1bdeafgch

842 annotation: Any 

843 if param.annotation is not param.empty: 1bdeafgch

844 annotation = param.annotation 1bdeafgch

845 else: 

846 annotation = str 1bdeafgch

847 main_type = annotation 1bdeafgch

848 is_list = False 1bdeafgch

849 is_tuple = False 1bdeafgch

850 parameter_type: Any = None 1bdeafgch

851 is_flag = None 1bdeafgch

852 origin = get_origin(main_type) 1bdeafgch

853 

854 if origin is not None: 1bdeafgch

855 # Handle SomeType | None and Optional[SomeType] 

856 if is_union(origin): 1bdeafgch

857 types = [] 1bdeafgch

858 for type_ in get_args(main_type): 1bdeafgch

859 if type_ is NoneType: 1bdeafgch

860 continue 1bdeafgch

861 types.append(type_) 1bdeafgch

862 assert len(types) == 1, "Typer Currently doesn't support Union types" 1bdeafgch

863 main_type = types[0] 1bdeafgch

864 origin = get_origin(main_type) 1bdeafgch

865 # Handle Tuples and Lists 

866 if lenient_issubclass(origin, list): 1bdeafgch

867 main_type = get_args(main_type)[0] 1bdeafgch

868 assert not get_origin(main_type), ( 1bdeafgch

869 "List types with complex sub-types are not currently supported" 

870 ) 

871 is_list = True 1bdeafgch

872 elif lenient_issubclass(origin, tuple): 1bdeafgch

873 types = [] 1bdeafgch

874 for type_ in get_args(main_type): 1bdeafgch

875 assert not get_origin(type_), ( 1bdeafgch

876 "Tuple types with complex sub-types are not currently supported" 

877 ) 

878 types.append( 1bdeafgch

879 get_click_type(annotation=type_, parameter_info=parameter_info) 

880 ) 

881 parameter_type = tuple(types) 1bdeafgch

882 is_tuple = True 1bdeafgch

883 if parameter_type is None: 1bdeafgch

884 parameter_type = get_click_type( 1bdeafgch

885 annotation=main_type, parameter_info=parameter_info 

886 ) 

887 convertor = determine_type_convertor(main_type) 1bdeafgch

888 if is_list: 1bdeafgch

889 convertor = generate_list_convertor( 1bdeafgch

890 convertor=convertor, default_value=default_value 

891 ) 

892 if is_tuple: 1bdeafgch

893 convertor = generate_tuple_convertor(get_args(main_type)) 1bdeafgch

894 if isinstance(parameter_info, OptionInfo): 1bdeafgch

895 if main_type is bool: 1bdeafgch

896 is_flag = True 1bdeafgch

897 # Click doesn't accept a flag of type bool, only None, and then it sets it 

898 # to bool internally 

899 parameter_type = None 1bdeafgch

900 default_option_name = get_command_name(param.name) 1bdeafgch

901 if is_flag: 1bdeafgch

902 default_option_declaration = ( 1bdeafgch

903 f"--{default_option_name}/--no-{default_option_name}" 

904 ) 

905 else: 

906 default_option_declaration = f"--{default_option_name}" 1bdeafgch

907 param_decls = [param.name] 1bdeafgch

908 if parameter_info.param_decls: 1bdeafgch

909 param_decls.extend(parameter_info.param_decls) 1bdeafgch

910 else: 

911 param_decls.append(default_option_declaration) 1bdeafgch

912 return ( 1bdeafgch

913 TyperOption( 

914 # Option 

915 param_decls=param_decls, 

916 show_default=parameter_info.show_default, 

917 prompt=parameter_info.prompt, 

918 confirmation_prompt=parameter_info.confirmation_prompt, 

919 prompt_required=parameter_info.prompt_required, 

920 hide_input=parameter_info.hide_input, 

921 is_flag=is_flag, 

922 multiple=is_list, 

923 count=parameter_info.count, 

924 allow_from_autoenv=parameter_info.allow_from_autoenv, 

925 type=parameter_type, 

926 help=parameter_info.help, 

927 hidden=parameter_info.hidden, 

928 show_choices=parameter_info.show_choices, 

929 show_envvar=parameter_info.show_envvar, 

930 # Parameter 

931 required=required, 

932 default=default_value, 

933 callback=get_param_callback( 

934 callback=parameter_info.callback, convertor=convertor 

935 ), 

936 metavar=parameter_info.metavar, 

937 expose_value=parameter_info.expose_value, 

938 is_eager=parameter_info.is_eager, 

939 envvar=parameter_info.envvar, 

940 shell_complete=parameter_info.shell_complete, 

941 autocompletion=get_param_completion(parameter_info.autocompletion), 

942 # Rich settings 

943 rich_help_panel=parameter_info.rich_help_panel, 

944 ), 

945 convertor, 

946 ) 

947 elif isinstance(parameter_info, ArgumentInfo): 1bdeafgch

948 param_decls = [param.name] 1bdeafgch

949 nargs = None 1bdeafgch

950 if is_list: 1bdeafgch

951 nargs = -1 1bdeafgch

952 return ( 1bdeafgch

953 TyperArgument( 

954 # Argument 

955 param_decls=param_decls, 

956 type=parameter_type, 

957 required=required, 

958 nargs=nargs, 

959 # TyperArgument 

960 show_default=parameter_info.show_default, 

961 show_choices=parameter_info.show_choices, 

962 show_envvar=parameter_info.show_envvar, 

963 help=parameter_info.help, 

964 hidden=parameter_info.hidden, 

965 # Parameter 

966 default=default_value, 

967 callback=get_param_callback( 

968 callback=parameter_info.callback, convertor=convertor 

969 ), 

970 metavar=parameter_info.metavar, 

971 expose_value=parameter_info.expose_value, 

972 is_eager=parameter_info.is_eager, 

973 envvar=parameter_info.envvar, 

974 shell_complete=parameter_info.shell_complete, 

975 autocompletion=get_param_completion(parameter_info.autocompletion), 

976 # Rich settings 

977 rich_help_panel=parameter_info.rich_help_panel, 

978 ), 

979 convertor, 

980 ) 

981 raise AssertionError("A click.Parameter should be returned") # pragma: no cover 

982 

983 

984def get_param_callback( 1bdeafgch

985 *, 

986 callback: Optional[Callable[..., Any]] = None, 

987 convertor: Optional[Callable[..., Any]] = None, 

988) -> Optional[Callable[..., Any]]: 

989 if not callback: 1bdeafgch

990 return None 1bdeafgch

991 parameters = get_params_from_function(callback) 1bdeafgch

992 ctx_name = None 1bdeafgch

993 click_param_name = None 1bdeafgch

994 value_name = None 1bdeafgch

995 untyped_names: list[str] = [] 1bdeafgch

996 for param_name, param_sig in parameters.items(): 1bdeafgch

997 if lenient_issubclass(param_sig.annotation, click.Context): 1bdeafgch

998 ctx_name = param_name 1bdeafgch

999 elif lenient_issubclass(param_sig.annotation, click.Parameter): 1bdeafgch

1000 click_param_name = param_name 1bdeafgch

1001 else: 

1002 untyped_names.append(param_name) 1bdeafgch

1003 # Extract value param name first 

1004 if untyped_names: 1bdeafgch

1005 value_name = untyped_names.pop() 1bdeafgch

1006 # If context and Click param were not typed (old/Click callback style) extract them 

1007 if untyped_names: 1bdeafgch

1008 if ctx_name is None: 1bdeafgch

1009 ctx_name = untyped_names.pop(0) 1bdeafgch

1010 if click_param_name is None: 1bdeafgch

1011 if untyped_names: 1bdeafgch

1012 click_param_name = untyped_names.pop(0) 1bdeafgch

1013 if untyped_names: 1bdeafgch

1014 raise click.ClickException( 1bdeafgch

1015 "Too many CLI parameter callback function parameters" 

1016 ) 

1017 

1018 def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: 1bdeafgch

1019 use_params: dict[str, Any] = {} 1bdeafgch

1020 if ctx_name: 1bdeafgch

1021 use_params[ctx_name] = ctx 1bdeafgch

1022 if click_param_name: 1bdeafgch

1023 use_params[click_param_name] = param 1bdeafgch

1024 if value_name: 1bdeafgch

1025 if convertor: 1bdeafgch

1026 use_value = convertor(value) 1bdeafgch

1027 else: 

1028 use_value = value 1bdeafgch

1029 use_params[value_name] = use_value 1bdeafgch

1030 return callback(**use_params) 1bdeafgch

1031 

1032 update_wrapper(wrapper, callback) 1bdeafgch

1033 return wrapper 1bdeafgch

1034 

1035 

1036def get_param_completion( 1bdeafgch

1037 callback: Optional[Callable[..., Any]] = None, 

1038) -> Optional[Callable[..., Any]]: 

1039 if not callback: 1bdeafgch

1040 return None 1bdeafgch

1041 parameters = get_params_from_function(callback) 1bdeafgch

1042 ctx_name = None 1bdeafgch

1043 args_name = None 1bdeafgch

1044 incomplete_name = None 1bdeafgch

1045 unassigned_params = list(parameters.values()) 1bdeafgch

1046 for param_sig in unassigned_params[:]: 1bdeafgch

1047 origin = get_origin(param_sig.annotation) 1bdeafgch

1048 if lenient_issubclass(param_sig.annotation, click.Context): 1bdeafgch

1049 ctx_name = param_sig.name 1bdeafgch

1050 unassigned_params.remove(param_sig) 1bdeafgch

1051 elif lenient_issubclass(origin, list): 1bdeafgch

1052 args_name = param_sig.name 1bdeafgch

1053 unassigned_params.remove(param_sig) 1bdeafgch

1054 elif lenient_issubclass(param_sig.annotation, str): 1bdeafgch

1055 incomplete_name = param_sig.name 1bdeafgch

1056 unassigned_params.remove(param_sig) 1bdeafgch

1057 # If there are still unassigned parameters (not typed), extract by name 

1058 for param_sig in unassigned_params[:]: 1bdeafgch

1059 if ctx_name is None and param_sig.name == "ctx": 1bdeafgch

1060 ctx_name = param_sig.name 1bdeafgch

1061 unassigned_params.remove(param_sig) 1bdeafgch

1062 elif args_name is None and param_sig.name == "args": 1bdeafgch

1063 args_name = param_sig.name 1bdeafgch

1064 unassigned_params.remove(param_sig) 1bdeafgch

1065 elif incomplete_name is None and param_sig.name == "incomplete": 1bdeafgch

1066 incomplete_name = param_sig.name 1bdeafgch

1067 unassigned_params.remove(param_sig) 1bdeafgch

1068 # Extract value param name first 

1069 if unassigned_params: 1bdeafgch

1070 show_params = " ".join([param.name for param in unassigned_params]) 1bdeafgch

1071 raise click.ClickException( 1bdeafgch

1072 f"Invalid autocompletion callback parameters: {show_params}" 

1073 ) 

1074 

1075 def wrapper(ctx: click.Context, args: list[str], incomplete: Optional[str]) -> Any: 1bdeafgch

1076 use_params: dict[str, Any] = {} 1bdeafgch

1077 if ctx_name: 1bdeafgch

1078 use_params[ctx_name] = ctx 1bdeafgch

1079 if args_name: 1bdeafgch

1080 use_params[args_name] = args 1bdeafgch

1081 if incomplete_name: 1bdeafgch

1082 use_params[incomplete_name] = incomplete 1bdeafgch

1083 return callback(**use_params) 1bdeafgch

1084 

1085 update_wrapper(wrapper, callback) 1bdeafgch

1086 return wrapper 1bdeafgch

1087 

1088 

1089def run(function: Callable[..., Any]) -> None: 1bdeafgch

1090 app = Typer(add_completion=False) 1bdeafgch

1091 app.command()(function) 1bdeafgch

1092 app() 1bdeafgch

1093 

1094 

1095def _is_macos() -> bool: 1bdeafgch

1096 return platform.system() == "Darwin" 1bdeafgch

1097 

1098 

1099def _is_linux_or_bsd() -> bool: 1bdeafgch

1100 if platform.system() == "Linux": 1bdeafgch

1101 return True 1bdeafgch

1102 

1103 return "BSD" in platform.system() 1bdeafgch

1104 

1105 

1106def launch(url: str, wait: bool = False, locate: bool = False) -> int: 1bdeafgch

1107 """This function launches the given URL (or filename) in the default 

1108 viewer application for this file type. If this is an executable, it 

1109 might launch the executable in a new session. The return value is 

1110 the exit code of the launched application. Usually, ``0`` indicates 

1111 success. 

1112 

1113 This function handles url in different operating systems separately: 

1114 - On macOS (Darwin), it uses the 'open' command. 

1115 - On Linux and BSD, it uses 'xdg-open' if available. 

1116 - On Windows (and other OSes), it uses the standard webbrowser module. 

1117 

1118 The function avoids, when possible, using the webbrowser module on Linux and macOS 

1119 to prevent spammy terminal messages from some browsers (e.g., Chrome). 

1120 

1121 Examples:: 

1122 

1123 typer.launch("https://typer.tiangolo.com/") 

1124 typer.launch("/my/downloaded/file", locate=True) 

1125 

1126 :param url: URL or filename of the thing to launch. 

1127 :param wait: Wait for the program to exit before returning. This 

1128 only works if the launched program blocks. In particular, 

1129 ``xdg-open`` on Linux does not block. 

1130 :param locate: if this is set to `True` then instead of launching the 

1131 application associated with the URL it will attempt to 

1132 launch a file manager with the file located. This 

1133 might have weird effects if the URL does not point to 

1134 the filesystem. 

1135 """ 

1136 

1137 if url.startswith("http://") or url.startswith("https://"): 1bdeafgch

1138 if _is_macos(): 1bdeafgch

1139 return subprocess.Popen( 1bdeafgch

1140 ["open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT 

1141 ).wait() 

1142 

1143 has_xdg_open = _is_linux_or_bsd() and shutil.which("xdg-open") is not None 1bdeafgch

1144 

1145 if has_xdg_open: 1bdeafgch

1146 return subprocess.Popen( 1bdeafgch

1147 ["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT 

1148 ).wait() 

1149 

1150 import webbrowser 1bdeafgch

1151 

1152 webbrowser.open(url) 1bdeafgch

1153 

1154 return 0 1bdeafgch

1155 

1156 else: 

1157 return click.launch(url) 1bdeafgch