Coverage for typer / main.py: 100%

490 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-26 21:46 +0000

1import inspect 1acdefbg

2import os 1acdefbg

3import platform 1acdefbg

4import shutil 1acdefbg

5import subprocess 1acdefbg

6import sys 1acdefbg

7import traceback 1acdefbg

8from collections.abc import Callable, Sequence 1acdefbg

9from datetime import datetime 1acdefbg

10from enum import Enum 1acdefbg

11from functools import update_wrapper 1acdefbg

12from pathlib import Path 1acdefbg

13from traceback import FrameSummary, StackSummary 1acdefbg

14from types import TracebackType 1acdefbg

15from typing import Annotated, Any 1acdefbg

16from uuid import UUID 1acdefbg

17 

18import click 1acdefbg

19from annotated_doc import Doc 1acdefbg

20from typer._types import TyperChoice 1acdefbg

21 

22from ._typing import get_args, get_origin, is_literal_type, is_union, literal_values 1acdefbg

23from .completion import get_completion_inspect_parameters 1acdefbg

24from .core import ( 1acdefbg

25 DEFAULT_MARKUP_MODE, 

26 HAS_RICH, 

27 MarkupMode, 

28 TyperArgument, 

29 TyperCommand, 

30 TyperGroup, 

31 TyperOption, 

32) 

33from .models import ( 1acdefbg

34 AnyType, 

35 ArgumentInfo, 

36 CommandFunctionType, 

37 CommandInfo, 

38 Default, 

39 DefaultPlaceholder, 

40 DeveloperExceptionConfig, 

41 FileBinaryRead, 

42 FileBinaryWrite, 

43 FileText, 

44 FileTextWrite, 

45 NoneType, 

46 OptionInfo, 

47 ParameterInfo, 

48 ParamMeta, 

49 Required, 

50 TyperInfo, 

51 TyperPath, 

52) 

53from .utils import get_params_from_function 1acdefbg

54 

55_original_except_hook = sys.excepthook 1acdefbg

56_typer_developer_exception_attr_name = "__typer_developer_exception__" 1acdefbg

57 

58 

59def except_hook( 1acdefbg

60 exc_type: type[BaseException], exc_value: BaseException, tb: TracebackType | None 

61) -> None: 

62 exception_config: DeveloperExceptionConfig | None = getattr( 1acdefbg

63 exc_value, _typer_developer_exception_attr_name, None 

64 ) 

65 standard_traceback = os.getenv( 1acdefbg

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

67 ) 

68 if ( 1ab

69 standard_traceback 

70 or not exception_config 

71 or not exception_config.pretty_exceptions_enable 

72 ): 

73 _original_except_hook(exc_type, exc_value, tb) 1acdefbg

74 return 1acdefbg

75 typer_path = os.path.dirname(__file__) 1acdefbg

76 click_path = os.path.dirname(click.__file__) 1acdefbg

77 internal_dir_names = [typer_path, click_path] 1acdefbg

78 exc = exc_value 1acdefbg

79 if HAS_RICH: 1acdefbg

80 from . import rich_utils 1acdefbg

81 

82 rich_tb = rich_utils.get_traceback(exc, exception_config, internal_dir_names) 1acdefbg

83 console_stderr = rich_utils._get_rich_console(stderr=True) 1acdefbg

84 console_stderr.print(rich_tb) 1acdefbg

85 return 1acdefbg

86 tb_exc = traceback.TracebackException.from_exception(exc) 1acdefbg

87 stack: list[FrameSummary] = [] 1acdefbg

88 for frame in tb_exc.stack: 1acdefbg

89 if any(frame.filename.startswith(path) for path in internal_dir_names): 1acdefbg

90 if not exception_config.pretty_exceptions_short: 1acdefbg

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

92 stack.append( 1acdefbg

93 traceback.FrameSummary( 

94 filename=frame.filename, 

95 lineno=frame.lineno, 

96 name=frame.name, 

97 line="", 

98 ) 

99 ) 

100 else: 

101 stack.append(frame) 1acdefbg

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

103 final_stack_summary = StackSummary.from_list(stack) 1acdefbg

104 tb_exc.stack = final_stack_summary 1acdefbg

105 for line in tb_exc.format(): 1acdefbg

106 print(line, file=sys.stderr) 1acdefbg

107 return 1acdefbg

108 

109 

110def get_install_completion_arguments() -> tuple[click.Parameter, click.Parameter]: 1acdefbg

111 install_param, show_param = get_completion_inspect_parameters() 1acdefbg

112 click_install_param, _ = get_click_param(install_param) 1acdefbg

113 click_show_param, _ = get_click_param(show_param) 1acdefbg

114 return click_install_param, click_show_param 1acdefbg

115 

116 

117class Typer: 1acdefbg

118 """ 

119 `Typer` main class, the main entrypoint to use Typer. 

120 

121 Read more in the 

122 [Typer docs for First Steps](https://typer.tiangolo.com/tutorial/typer-app/). 

123 

124 ## Example 

125 

126 ```python 

127 import typer 

128 

129 app = typer.Typer() 

130 ``` 

131 """ 

132 

133 def __init__( 1acdefbg

134 self, 

135 *, 

136 name: Annotated[ 

137 str | None, 

138 Doc( 

139 """ 

140 The name of this application. 

141 Mostly used to set the name for [subcommands](https://typer.tiangolo.com/tutorial/subcommands/), in which case it can be overridden by `add_typer(name=...)`. 

142 

143 **Example** 

144 

145 ```python 

146 import typer 

147 

148 app = typer.Typer(name="users") 

149 ``` 

150 """ 

151 ), 

152 ] = Default(None), 

153 cls: Annotated[ 

154 type[TyperGroup] | None, 

155 Doc( 

156 """ 

157 The class of this app. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. 

158 Otherwise, should be a subtype of `TyperGroup`. 

159 

160 **Example** 

161 

162 ```python 

163 import typer 

164 

165 app = typer.Typer(cls=TyperGroup) 

166 ``` 

167 """ 

168 ), 

169 ] = Default(None), 

170 invoke_without_command: Annotated[ 

171 bool, 

172 Doc( 

173 """ 

174 By setting this to `True`, you can make sure a callback is executed even when no subcommand is provided. 

175 

176 **Example** 

177 

178 ```python 

179 import typer 

180 

181 app = typer.Typer(invoke_without_command=True) 

182 ``` 

183 """ 

184 ), 

185 ] = Default(False), 

186 no_args_is_help: Annotated[ 

187 bool, 

188 Doc( 

189 """ 

190 If this is set to `True`, running a command without any arguments will automatically show the help page. 

191 

192 **Example** 

193 

194 ```python 

195 import typer 

196 

197 app = typer.Typer(no_args_is_help=True) 

198 ``` 

199 """ 

200 ), 

201 ] = Default(False), 

202 subcommand_metavar: Annotated[ 

203 str | None, 

204 Doc( 

205 """ 

206 **Note**: you probably shouldn't use this parameter, it is inherited 

207 from Click and supported for compatibility. 

208 

209 --- 

210 

211 How to represent the subcommand argument in help. 

212 """ 

213 ), 

214 ] = Default(None), 

215 chain: Annotated[ 

216 bool, 

217 Doc( 

218 """ 

219 **Note**: you probably shouldn't use this parameter, it is inherited 

220 from Click and supported for compatibility. 

221 

222 --- 

223 

224 Allow passing more than one subcommand argument. 

225 """ 

226 ), 

227 ] = Default(False), 

228 result_callback: Annotated[ 

229 Callable[..., Any] | None, 

230 Doc( 

231 """ 

232 **Note**: you probably shouldn't use this parameter, it is inherited 

233 from Click and supported for compatibility. 

234 

235 --- 

236 

237 A function to call after the group's and subcommand's callbacks. 

238 """ 

239 ), 

240 ] = Default(None), 

241 # Command 

242 context_settings: Annotated[ 

243 dict[Any, Any] | None, 

244 Doc( 

245 """ 

246 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). 

247 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context). 

248 

249 **Example** 

250 

251 ```python 

252 import typer 

253 

254 app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}) 

255 ``` 

256 """ 

257 ), 

258 ] = Default(None), 

259 callback: Annotated[ 

260 Callable[..., Any] | None, 

261 Doc( 

262 """ 

263 Add a callback to the main Typer app. Can be overridden with `@app.callback()`. 

264 See [the tutorial about callbacks](https://typer.tiangolo.com/tutorial/commands/callback/) for more details. 

265 

266 **Example** 

267 

268 ```python 

269 import typer 

270 

271 def callback(): 

272 print("Running a command") 

273 

274 app = typer.Typer(callback=callback) 

275 ``` 

276 """ 

277 ), 

278 ] = Default(None), 

279 help: Annotated[ 

280 str | None, 

281 Doc( 

282 """ 

283 Help text for the main Typer app. 

284 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help, 

285 and which one takes priority. 

286 

287 **Example** 

288 

289 ```python 

290 import typer 

291 

292 app = typer.Typer(help="Some help.") 

293 ``` 

294 """ 

295 ), 

296 ] = Default(None), 

297 epilog: Annotated[ 

298 str | None, 

299 Doc( 

300 """ 

301 Text that will be printed right after the help text. 

302 

303 **Example** 

304 

305 ```python 

306 import typer 

307 

308 app = typer.Typer(epilog="May the force be with you") 

309 ``` 

310 """ 

311 ), 

312 ] = Default(None), 

313 short_help: Annotated[ 

314 str | None, 

315 Doc( 

316 """ 

317 A shortened version of the help text that can be used e.g. in the help table listing subcommands. 

318 When not defined, the normal `help` text will be used instead. 

319 

320 **Example** 

321 

322 ```python 

323 import typer 

324 

325 app = typer.Typer(help="A lot of explanation about user management", short_help="user management") 

326 ``` 

327 """ 

328 ), 

329 ] = Default(None), 

330 options_metavar: Annotated[ 

331 str, 

332 Doc( 

333 """ 

334 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`. 

335 Set `options_metavar` to change this into a different string. 

336 

337 **Example** 

338 

339 ```python 

340 import typer 

341 

342 app = typer.Typer(options_metavar="[OPTS]") 

343 ``` 

344 """ 

345 ), 

346 ] = Default("[OPTIONS]"), 

347 add_help_option: Annotated[ 

348 bool, 

349 Doc( 

350 """ 

351 **Note**: you probably shouldn't use this parameter, it is inherited 

352 from Click and supported for compatibility. 

353 

354 --- 

355 

356 By default each command registers a `--help` option. This can be disabled by this parameter. 

357 """ 

358 ), 

359 ] = Default(True), 

360 hidden: Annotated[ 

361 bool, 

362 Doc( 

363 """ 

364 Hide this command from help outputs. `False` by default. 

365 

366 **Example** 

367 

368 ```python 

369 import typer 

370 

371 app = typer.Typer(hidden=True) 

372 ``` 

373 """ 

374 ), 

375 ] = Default(False), 

376 deprecated: Annotated[ 

377 bool, 

378 Doc( 

379 """ 

380 Mark this command as being deprecated in the help text. `False` by default. 

381 

382 **Example** 

383 

384 ```python 

385 import typer 

386 

387 app = typer.Typer(deprecated=True) 

388 ``` 

389 """ 

390 ), 

391 ] = Default(False), 

392 add_completion: Annotated[ 

393 bool, 

394 Doc( 

395 """ 

396 Toggle whether or not to add the `--install-completion` and `--show-completion` options to the app. 

397 Set to `True` by default. 

398 

399 **Example** 

400 

401 ```python 

402 import typer 

403 

404 app = typer.Typer(add_completion=False) 

405 ``` 

406 """ 

407 ), 

408 ] = True, 

409 # Rich settings 

410 rich_markup_mode: Annotated[ 

411 MarkupMode, 

412 Doc( 

413 """ 

414 Enable markup text if you have Rich installed. This can be set to `"markdown"`, `"rich"`, or `None`. 

415 By default, `rich_markup_mode` is `None` if Rich is not installed, and `"rich"` if it is installed. 

416 See [the tutorial on help formatting](https://typer.tiangolo.com/tutorial/commands/help/#rich-markdown-and-markup) for more information. 

417 

418 **Example** 

419 

420 ```python 

421 import typer 

422 

423 app = typer.Typer(rich_markup_mode="rich") 

424 ``` 

425 """ 

426 ), 

427 ] = DEFAULT_MARKUP_MODE, 

428 rich_help_panel: Annotated[ 

429 str | None, 

430 Doc( 

431 """ 

432 Set the panel name of the command when the help is printed with Rich. 

433 

434 **Example** 

435 

436 ```python 

437 import typer 

438 

439 app = typer.Typer(rich_help_panel="Utils and Configs") 

440 ``` 

441 """ 

442 ), 

443 ] = Default(None), 

444 suggest_commands: Annotated[ 

445 bool, 

446 Doc( 

447 """ 

448 As of version 0.20.0, Typer provides [support for mistyped command names](https://typer.tiangolo.com/tutorial/commands/help/#suggest-commands) by printing helpful suggestions. 

449 You can turn this setting off with `suggest_commands`: 

450 

451 **Example** 

452 

453 ```python 

454 import typer 

455 

456 app = typer.Typer(suggest_commands=False) 

457 ``` 

458 """ 

459 ), 

460 ] = True, 

461 pretty_exceptions_enable: Annotated[ 

462 bool, 

463 Doc( 

464 """ 

465 If you want to disable [pretty exceptions with Rich](https://typer.tiangolo.com/tutorial/exceptions/#exceptions-with-rich), 

466 you can set `pretty_exceptions_enable` to `False`. When doing so, you will see the usual standard exception trace. 

467 

468 **Example** 

469 

470 ```python 

471 import typer 

472 

473 app = typer.Typer(pretty_exceptions_enable=False) 

474 ``` 

475 """ 

476 ), 

477 ] = True, 

478 pretty_exceptions_show_locals: Annotated[ 

479 bool, 

480 Doc( 

481 """ 

482 If Rich is installed, [error messages](https://typer.tiangolo.com/tutorial/exceptions/#exceptions-and-errors) 

483 will be nicely printed. 

484 

485 If you set `pretty_exceptions_show_locals=True` it will also include the values of local variables for easy debugging. 

486 

487 However, if such a variable contains delicate information, you should consider leaving `pretty_exceptions_show_locals=False` 

488 (the default) to `False` to enhance security. 

489 

490 **Example** 

491 

492 ```python 

493 import typer 

494 

495 app = typer.Typer(pretty_exceptions_show_locals=True) 

496 ``` 

497 """ 

498 ), 

499 ] = False, 

500 pretty_exceptions_short: Annotated[ 

501 bool, 

502 Doc( 

503 """ 

504 By default, [pretty exceptions formatted with Rich](https://typer.tiangolo.com/tutorial/exceptions/#exceptions-with-rich) hide the long stack trace. 

505 If you want to show the full trace instead, you can set the parameter `pretty_exceptions_short` to `False`: 

506 

507 **Example** 

508 

509 ```python 

510 import typer 

511 

512 app = typer.Typer(pretty_exceptions_short=False) 

513 ``` 

514 """ 

515 ), 

516 ] = True, 

517 ): 

518 self._add_completion = add_completion 1acdefbg

519 self.rich_markup_mode: MarkupMode = rich_markup_mode 1acdefbg

520 self.rich_help_panel = rich_help_panel 1acdefbg

521 self.suggest_commands = suggest_commands 1acdefbg

522 self.pretty_exceptions_enable = pretty_exceptions_enable 1acdefbg

523 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1acdefbg

524 self.pretty_exceptions_short = pretty_exceptions_short 1acdefbg

525 self.info = TyperInfo( 1acdefbg

526 name=name, 

527 cls=cls, 

528 invoke_without_command=invoke_without_command, 

529 no_args_is_help=no_args_is_help, 

530 subcommand_metavar=subcommand_metavar, 

531 chain=chain, 

532 result_callback=result_callback, 

533 context_settings=context_settings, 

534 callback=callback, 

535 help=help, 

536 epilog=epilog, 

537 short_help=short_help, 

538 options_metavar=options_metavar, 

539 add_help_option=add_help_option, 

540 hidden=hidden, 

541 deprecated=deprecated, 

542 ) 

543 self.registered_groups: list[TyperInfo] = [] 1acdefbg

544 self.registered_commands: list[CommandInfo] = [] 1acdefbg

545 self.registered_callback: TyperInfo | None = None 1acdefbg

546 

547 def callback( 1acdefbg

548 self, 

549 *, 

550 cls: Annotated[ 

551 type[TyperGroup] | None, 

552 Doc( 

553 """ 

554 The class of this app. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. 

555 Otherwise, should be a subtype of `TyperGroup`. 

556 """ 

557 ), 

558 ] = Default(None), 

559 invoke_without_command: Annotated[ 

560 bool, 

561 Doc( 

562 """ 

563 By setting this to `True`, you can make sure a callback is executed even when no subcommand is provided. 

564 """ 

565 ), 

566 ] = Default(False), 

567 no_args_is_help: Annotated[ 

568 bool, 

569 Doc( 

570 """ 

571 If this is set to `True`, running a command without any arguments will automatically show the help page. 

572 """ 

573 ), 

574 ] = Default(False), 

575 subcommand_metavar: Annotated[ 

576 str | None, 

577 Doc( 

578 """ 

579 **Note**: you probably shouldn't use this parameter, it is inherited 

580 from Click and supported for compatibility. 

581 

582 --- 

583 

584 How to represent the subcommand argument in help. 

585 """ 

586 ), 

587 ] = Default(None), 

588 chain: Annotated[ 

589 bool, 

590 Doc( 

591 """ 

592 **Note**: you probably shouldn't use this parameter, it is inherited 

593 from Click and supported for compatibility. 

594 

595 --- 

596 

597 Allow passing more than one subcommand argument. 

598 """ 

599 ), 

600 ] = Default(False), 

601 result_callback: Annotated[ 

602 Callable[..., Any] | None, 

603 Doc( 

604 """ 

605 **Note**: you probably shouldn't use this parameter, it is inherited 

606 from Click and supported for compatibility. 

607 

608 --- 

609 

610 A function to call after the group's and subcommand's callbacks. 

611 """ 

612 ), 

613 ] = Default(None), 

614 # Command 

615 context_settings: Annotated[ 

616 dict[Any, Any] | None, 

617 Doc( 

618 """ 

619 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). 

620 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context). 

621 """ 

622 ), 

623 ] = Default(None), 

624 help: Annotated[ 

625 str | None, 

626 Doc( 

627 """ 

628 Help text for the command. 

629 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help, 

630 and which one takes priority. 

631 """ 

632 ), 

633 ] = Default(None), 

634 epilog: Annotated[ 

635 str | None, 

636 Doc( 

637 """ 

638 Text that will be printed right after the help text. 

639 """ 

640 ), 

641 ] = Default(None), 

642 short_help: Annotated[ 

643 str | None, 

644 Doc( 

645 """ 

646 A shortened version of the help text that can be used e.g. in the help table listing subcommands. 

647 When not defined, the normal `help` text will be used instead. 

648 """ 

649 ), 

650 ] = Default(None), 

651 options_metavar: Annotated[ 

652 str | None, 

653 Doc( 

654 """ 

655 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`. 

656 Set `options_metavar` to change this into a different string. When `None`, the default value will be used. 

657 """ 

658 ), 

659 ] = Default(None), 

660 add_help_option: Annotated[ 

661 bool, 

662 Doc( 

663 """ 

664 **Note**: you probably shouldn't use this parameter, it is inherited 

665 from Click and supported for compatibility. 

666 

667 --- 

668 

669 By default each command registers a `--help` option. This can be disabled by this parameter. 

670 """ 

671 ), 

672 ] = Default(True), 

673 hidden: Annotated[ 

674 bool, 

675 Doc( 

676 """ 

677 Hide this command from help outputs. `False` by default. 

678 """ 

679 ), 

680 ] = Default(False), 

681 deprecated: Annotated[ 

682 bool, 

683 Doc( 

684 """ 

685 Mark this command as deprecated in the help text. `False` by default. 

686 """ 

687 ), 

688 ] = Default(False), 

689 # Rich settings 

690 rich_help_panel: Annotated[ 

691 str | None, 

692 Doc( 

693 """ 

694 Set the panel name of the command when the help is printed with Rich. 

695 """ 

696 ), 

697 ] = Default(None), 

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

699 """ 

700 Using the decorator `@app.callback`, you can declare the CLI parameters for the main CLI application. 

701 

702 Read more in the 

703 [Typer docs for Callbacks](https://typer.tiangolo.com/tutorial/commands/callback/). 

704 

705 ## Example 

706 

707 ```python 

708 import typer 

709 

710 app = typer.Typer() 

711 state = {"verbose": False} 

712 

713 @app.callback() 

714 def main(verbose: bool = False): 

715 if verbose: 

716 print("Will write verbose output") 

717 state["verbose"] = True 

718 

719 @app.command() 

720 def delete(username: str): 

721 # define subcommand 

722 ... 

723 ``` 

724 """ 

725 

726 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1acdefbg

727 self.registered_callback = TyperInfo( 1acdefbg

728 cls=cls, 

729 invoke_without_command=invoke_without_command, 

730 no_args_is_help=no_args_is_help, 

731 subcommand_metavar=subcommand_metavar, 

732 chain=chain, 

733 result_callback=result_callback, 

734 context_settings=context_settings, 

735 callback=f, 

736 help=help, 

737 epilog=epilog, 

738 short_help=short_help, 

739 options_metavar=( 

740 options_metavar or self._info_val_str("options_metavar") 

741 ), 

742 add_help_option=add_help_option, 

743 hidden=hidden, 

744 deprecated=deprecated, 

745 rich_help_panel=rich_help_panel, 

746 ) 

747 return f 1acdefbg

748 

749 return decorator 1acdefbg

750 

751 def command( 1acdefbg

752 self, 

753 name: Annotated[ 

754 str | None, 

755 Doc( 

756 """ 

757 The name of this command. 

758 """ 

759 ), 

760 ] = None, 

761 *, 

762 cls: Annotated[ 

763 type[TyperCommand] | None, 

764 Doc( 

765 """ 

766 The class of this command. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. 

767 Otherwise, should be a subtype of `TyperCommand`. 

768 """ 

769 ), 

770 ] = None, 

771 context_settings: Annotated[ 

772 dict[Any, Any] | None, 

773 Doc( 

774 """ 

775 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). 

776 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context). 

777 """ 

778 ), 

779 ] = None, 

780 help: Annotated[ 

781 str | None, 

782 Doc( 

783 """ 

784 Help text for the command. 

785 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help, 

786 and which one takes priority. 

787 """ 

788 ), 

789 ] = None, 

790 epilog: Annotated[ 

791 str | None, 

792 Doc( 

793 """ 

794 Text that will be printed right after the help text. 

795 """ 

796 ), 

797 ] = None, 

798 short_help: Annotated[ 

799 str | None, 

800 Doc( 

801 """ 

802 A shortened version of the help text that can be used e.g. in the help table listing subcommands. 

803 When not defined, the normal `help` text will be used instead. 

804 """ 

805 ), 

806 ] = None, 

807 options_metavar: Annotated[ 

808 str | None, 

809 Doc( 

810 """ 

811 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`. 

812 Set `options_metavar` to change this into a different string. When `None`, the default value will be used. 

813 """ 

814 ), 

815 ] = Default(None), 

816 add_help_option: Annotated[ 

817 bool, 

818 Doc( 

819 """ 

820 **Note**: you probably shouldn't use this parameter, it is inherited 

821 from Click and supported for compatibility. 

822 

823 --- 

824 

825 By default each command registers a `--help` option. This can be disabled by this parameter. 

826 """ 

827 ), 

828 ] = True, 

829 no_args_is_help: Annotated[ 

830 bool, 

831 Doc( 

832 """ 

833 If this is set to `True`, running a command without any arguments will automatically show the help page. 

834 """ 

835 ), 

836 ] = False, 

837 hidden: Annotated[ 

838 bool, 

839 Doc( 

840 """ 

841 Hide this command from help outputs. `False` by default. 

842 """ 

843 ), 

844 ] = False, 

845 deprecated: Annotated[ 

846 bool, 

847 Doc( 

848 """ 

849 Mark this command as deprecated in the help outputs. `False` by default. 

850 """ 

851 ), 

852 ] = False, 

853 # Rich settings 

854 rich_help_panel: Annotated[ 

855 str | None, 

856 Doc( 

857 """ 

858 Set the panel name of the command when the help is printed with Rich. 

859 """ 

860 ), 

861 ] = Default(None), 

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

863 """ 

864 Using the decorator `@app.command`, you can define a subcommand of the previously defined Typer app. 

865 

866 Read more in the 

867 [Typer docs for Commands](https://typer.tiangolo.com/tutorial/commands/). 

868 

869 ## Example 

870 

871 ```python 

872 import typer 

873 

874 app = typer.Typer() 

875 

876 @app.command() 

877 def create(): 

878 print("Creating user: Hiro Hamada") 

879 

880 @app.command() 

881 def delete(): 

882 print("Deleting user: Hiro Hamada") 

883 ``` 

884 """ 

885 if cls is None: 1acdefbg

886 cls = TyperCommand 1acdefbg

887 

888 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1acdefbg

889 self.registered_commands.append( 1acdefbg

890 CommandInfo( 

891 name=name, 

892 cls=cls, 

893 context_settings=context_settings, 

894 callback=f, 

895 help=help, 

896 epilog=epilog, 

897 short_help=short_help, 

898 options_metavar=( 

899 options_metavar or self._info_val_str("options_metavar") 

900 ), 

901 add_help_option=add_help_option, 

902 no_args_is_help=no_args_is_help, 

903 hidden=hidden, 

904 deprecated=deprecated, 

905 # Rich settings 

906 rich_help_panel=rich_help_panel, 

907 ) 

908 ) 

909 return f 1acdefbg

910 

911 return decorator 1acdefbg

912 

913 def add_typer( 1acdefbg

914 self, 

915 typer_instance: "Typer", 

916 *, 

917 name: Annotated[ 

918 str | None, 

919 Doc( 

920 """ 

921 The name of this subcommand. 

922 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's name, 

923 and which one takes priority. 

924 """ 

925 ), 

926 ] = Default(None), 

927 cls: Annotated[ 

928 type[TyperGroup] | None, 

929 Doc( 

930 """ 

931 The class of this subcommand. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. 

932 Otherwise, should be a subtype of `TyperGroup`. 

933 """ 

934 ), 

935 ] = Default(None), 

936 invoke_without_command: Annotated[ 

937 bool, 

938 Doc( 

939 """ 

940 By setting this to `True`, you can make sure a callback is executed even when no subcommand is provided. 

941 """ 

942 ), 

943 ] = Default(False), 

944 no_args_is_help: Annotated[ 

945 bool, 

946 Doc( 

947 """ 

948 If this is set to `True`, running a command without any arguments will automatically show the help page. 

949 """ 

950 ), 

951 ] = Default(False), 

952 subcommand_metavar: Annotated[ 

953 str | None, 

954 Doc( 

955 """ 

956 **Note**: you probably shouldn't use this parameter, it is inherited 

957 from Click and supported for compatibility. 

958 

959 --- 

960 

961 How to represent the subcommand argument in help. 

962 """ 

963 ), 

964 ] = Default(None), 

965 chain: Annotated[ 

966 bool, 

967 Doc( 

968 """ 

969 **Note**: you probably shouldn't use this parameter, it is inherited 

970 from Click and supported for compatibility. 

971 

972 --- 

973 

974 Allow passing more than one subcommand argument. 

975 """ 

976 ), 

977 ] = Default(False), 

978 result_callback: Annotated[ 

979 Callable[..., Any] | None, 

980 Doc( 

981 """ 

982 **Note**: you probably shouldn't use this parameter, it is inherited 

983 from Click and supported for compatibility. 

984 

985 --- 

986 

987 A function to call after the group's and subcommand's callbacks. 

988 """ 

989 ), 

990 ] = Default(None), 

991 # Command 

992 context_settings: Annotated[ 

993 dict[Any, Any] | None, 

994 Doc( 

995 """ 

996 Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). 

997 Available configurations can be found in the docs for Click's `Context` [here](https://click.palletsprojects.com/en/stable/api/#context). 

998 """ 

999 ), 

1000 ] = Default(None), 

1001 callback: Annotated[ 

1002 Callable[..., Any] | None, 

1003 Doc( 

1004 """ 

1005 Add a callback to this app. 

1006 See [the tutorial about callbacks](https://typer.tiangolo.com/tutorial/commands/callback/) for more details. 

1007 """ 

1008 ), 

1009 ] = Default(None), 

1010 help: Annotated[ 

1011 str | None, 

1012 Doc( 

1013 """ 

1014 Help text for the subcommand. 

1015 See [the tutorial about name and help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help) for different ways of setting a command's help, 

1016 and which one takes priority. 

1017 """ 

1018 ), 

1019 ] = Default(None), 

1020 epilog: Annotated[ 

1021 str | None, 

1022 Doc( 

1023 """ 

1024 Text that will be printed right after the help text. 

1025 """ 

1026 ), 

1027 ] = Default(None), 

1028 short_help: Annotated[ 

1029 str | None, 

1030 Doc( 

1031 """ 

1032 A shortened version of the help text that can be used e.g. in the help table listing subcommands. 

1033 When not defined, the normal `help` text will be used instead. 

1034 """ 

1035 ), 

1036 ] = Default(None), 

1037 options_metavar: Annotated[ 

1038 str | None, 

1039 Doc( 

1040 """ 

1041 In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`. 

1042 Set `options_metavar` to change this into a different string. When `None`, the default value will be used. 

1043 """ 

1044 ), 

1045 ] = Default(None), 

1046 add_help_option: Annotated[ 

1047 bool, 

1048 Doc( 

1049 """ 

1050 **Note**: you probably shouldn't use this parameter, it is inherited 

1051 from Click and supported for compatibility. 

1052 

1053 --- 

1054 

1055 By default each command registers a `--help` option. This can be disabled by this parameter. 

1056 """ 

1057 ), 

1058 ] = Default(True), 

1059 hidden: Annotated[ 

1060 bool, 

1061 Doc( 

1062 """ 

1063 Hide this command from help outputs. `False` by default. 

1064 """ 

1065 ), 

1066 ] = Default(False), 

1067 deprecated: Annotated[ 

1068 bool, 

1069 Doc( 

1070 """ 

1071 Mark this command as deprecated in the help outputs. `False` by default. 

1072 """ 

1073 ), 

1074 ] = False, 

1075 # Rich settings 

1076 rich_help_panel: Annotated[ 

1077 str | None, 

1078 Doc( 

1079 """ 

1080 Set the panel name of the command when the help is printed with Rich. 

1081 """ 

1082 ), 

1083 ] = Default(None), 

1084 ) -> None: 

1085 """ 

1086 Add subcommands to the main app using `app.add_typer()`. 

1087 Subcommands may be defined in separate modules, ensuring clean separation of code by functionality. 

1088 

1089 Read more in the 

1090 [Typer docs for SubCommands](https://typer.tiangolo.com/tutorial/subcommands/add-typer/). 

1091 

1092 ## Example 

1093 

1094 ```python 

1095 import typer 

1096 

1097 from .add import app as add_app 

1098 from .delete import app as delete_app 

1099 

1100 app = typer.Typer() 

1101 

1102 app.add_typer(add_app) 

1103 app.add_typer(delete_app) 

1104 ``` 

1105 """ 

1106 self.registered_groups.append( 1acdefbg

1107 TyperInfo( 

1108 typer_instance, 

1109 name=name, 

1110 cls=cls, 

1111 invoke_without_command=invoke_without_command, 

1112 no_args_is_help=no_args_is_help, 

1113 subcommand_metavar=subcommand_metavar, 

1114 chain=chain, 

1115 result_callback=result_callback, 

1116 context_settings=context_settings, 

1117 callback=callback, 

1118 help=help, 

1119 epilog=epilog, 

1120 short_help=short_help, 

1121 options_metavar=( 

1122 options_metavar or self._info_val_str("options_metavar") 

1123 ), 

1124 add_help_option=add_help_option, 

1125 hidden=hidden, 

1126 deprecated=deprecated, 

1127 rich_help_panel=rich_help_panel, 

1128 ) 

1129 ) 

1130 

1131 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1acdefbg

1132 if sys.excepthook != except_hook: 1acdefbg

1133 sys.excepthook = except_hook 1acdefbg

1134 try: 1acdefbg

1135 return get_command(self)(*args, **kwargs) 1acdefbg

1136 except Exception as e: 1acdefbg

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

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

1139 # raise custom_exc from e 

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

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

1142 # actual error last. 

1143 setattr( 1acdefbg

1144 e, 

1145 _typer_developer_exception_attr_name, 

1146 DeveloperExceptionConfig( 

1147 pretty_exceptions_enable=self.pretty_exceptions_enable, 

1148 pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, 

1149 pretty_exceptions_short=self.pretty_exceptions_short, 

1150 ), 

1151 ) 

1152 raise e 1acdefbg

1153 

1154 def _info_val_str(self, name: str) -> str: 1acdefbg

1155 val = getattr(self.info, name) 1acdefbg

1156 val_str = val.value if isinstance(val, DefaultPlaceholder) else val 1acdefbg

1157 assert isinstance(val_str, str) 1acdefbg

1158 return val_str 1acdefbg

1159 

1160 

1161def get_group(typer_instance: Typer) -> TyperGroup: 1acdefbg

1162 group = get_group_from_info( 1acdefbg

1163 TyperInfo(typer_instance), 

1164 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

1165 rich_markup_mode=typer_instance.rich_markup_mode, 

1166 suggest_commands=typer_instance.suggest_commands, 

1167 ) 

1168 return group 1acdefbg

1169 

1170 

1171def get_command(typer_instance: Typer) -> click.Command: 1acdefbg

1172 if typer_instance._add_completion: 1acdefbg

1173 click_install_param, click_show_param = get_install_completion_arguments() 1acdefbg

1174 if ( 1ab

1175 typer_instance.registered_callback 

1176 or typer_instance.info.callback 

1177 or typer_instance.registered_groups 

1178 or len(typer_instance.registered_commands) > 1 

1179 ): 

1180 # Create a Group 

1181 click_command: click.Command = get_group(typer_instance) 1acdefbg

1182 if typer_instance._add_completion: 1acdefbg

1183 click_command.params.append(click_install_param) 1acdefbg

1184 click_command.params.append(click_show_param) 1acdefbg

1185 return click_command 1acdefbg

1186 elif len(typer_instance.registered_commands) == 1: 1acdefbg

1187 # Create a single Command 

1188 single_command = typer_instance.registered_commands[0] 1acdefbg

1189 

1190 if not single_command.context_settings and not isinstance( 1acdefbg

1191 typer_instance.info.context_settings, DefaultPlaceholder 

1192 ): 

1193 single_command.context_settings = typer_instance.info.context_settings 1acdefbg

1194 

1195 click_command = get_command_from_info( 1acdefbg

1196 single_command, 

1197 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

1198 rich_markup_mode=typer_instance.rich_markup_mode, 

1199 ) 

1200 if typer_instance._add_completion: 1acdefbg

1201 click_command.params.append(click_install_param) 1acdefbg

1202 click_command.params.append(click_show_param) 1acdefbg

1203 return click_command 1acdefbg

1204 raise RuntimeError( 

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

1206 ) # pragma: no cover 

1207 

1208 

1209def solve_typer_info_help(typer_info: TyperInfo) -> str: 1acdefbg

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

1211 if not isinstance(typer_info.help, DefaultPlaceholder): 1acdefbg

1212 return inspect.cleandoc(typer_info.help or "") 1acdefbg

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

1214 if typer_info.typer_instance and typer_info.typer_instance.registered_callback: 1acdefbg

1215 callback_help = typer_info.typer_instance.registered_callback.help 1acdefbg

1216 if not isinstance(callback_help, DefaultPlaceholder): 1acdefbg

1217 return inspect.cleandoc(callback_help or "") 1acdefbg

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

1219 if typer_info.typer_instance and typer_info.typer_instance.info: 1acdefbg

1220 instance_help = typer_info.typer_instance.info.help 1acdefbg

1221 if not isinstance(instance_help, DefaultPlaceholder): 1acdefbg

1222 return inspect.cleandoc(instance_help or "") 1acdefbg

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

1224 if typer_info.callback: 1acdefbg

1225 doc = inspect.getdoc(typer_info.callback) 1acdefbg

1226 if doc: 1acdefbg

1227 return doc 1acdefbg

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

1229 if typer_info.typer_instance and typer_info.typer_instance.registered_callback: 1acdefbg

1230 callback = typer_info.typer_instance.registered_callback.callback 1acdefbg

1231 if not isinstance(callback, DefaultPlaceholder): 1acdefbg

1232 doc = inspect.getdoc(callback or "") 1acdefbg

1233 if doc: 1acdefbg

1234 return doc 1acdefbg

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

1236 if typer_info.typer_instance and typer_info.typer_instance.info: 1acdefbg

1237 instance_callback = typer_info.typer_instance.info.callback 1acdefbg

1238 if not isinstance(instance_callback, DefaultPlaceholder): 1acdefbg

1239 doc = inspect.getdoc(instance_callback) 1acdefbg

1240 if doc: 1acdefbg

1241 return doc 1acdefbg

1242 # Value not set, use the default 

1243 return typer_info.help.value 1acdefbg

1244 

1245 

1246def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1acdefbg

1247 values: dict[str, Any] = {} 1acdefbg

1248 for name, value in typer_info.__dict__.items(): 1acdefbg

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

1250 if not isinstance(value, DefaultPlaceholder): 1acdefbg

1251 values[name] = value 1acdefbg

1252 continue 1acdefbg

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

1254 try: 1acdefbg

1255 callback_value = getattr( 1acdefbg

1256 typer_info.typer_instance.registered_callback, # type: ignore 

1257 name, 

1258 ) 

1259 if not isinstance(callback_value, DefaultPlaceholder): 1acdefbg

1260 values[name] = callback_value 1acdefbg

1261 continue 1acdefbg

1262 except AttributeError: 1acdefbg

1263 pass 1acdefbg

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

1265 try: 1acdefbg

1266 instance_value = getattr( 1acdefbg

1267 typer_info.typer_instance.info, # type: ignore 

1268 name, 

1269 ) 

1270 if not isinstance(instance_value, DefaultPlaceholder): 1acdefbg

1271 values[name] = instance_value 1acdefbg

1272 continue 1acdefbg

1273 except AttributeError: 1acdefbg

1274 pass 1acdefbg

1275 # Value not set, use the default 

1276 values[name] = value.value 1acdefbg

1277 values["help"] = solve_typer_info_help(typer_info) 1acdefbg

1278 return TyperInfo(**values) 1acdefbg

1279 

1280 

1281def get_group_from_info( 1acdefbg

1282 group_info: TyperInfo, 

1283 *, 

1284 pretty_exceptions_short: bool, 

1285 suggest_commands: bool, 

1286 rich_markup_mode: MarkupMode, 

1287) -> TyperGroup: 

1288 assert group_info.typer_instance, ( 1acdefbg

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

1290 ) 

1291 commands: dict[str, click.Command] = {} 1acdefbg

1292 for command_info in group_info.typer_instance.registered_commands: 1acdefbg

1293 command = get_command_from_info( 1acdefbg

1294 command_info=command_info, 

1295 pretty_exceptions_short=pretty_exceptions_short, 

1296 rich_markup_mode=rich_markup_mode, 

1297 ) 

1298 if command.name: 1acdefbg

1299 commands[command.name] = command 1acdefbg

1300 for sub_group_info in group_info.typer_instance.registered_groups: 1acdefbg

1301 sub_group = get_group_from_info( 1acdefbg

1302 sub_group_info, 

1303 pretty_exceptions_short=pretty_exceptions_short, 

1304 rich_markup_mode=rich_markup_mode, 

1305 suggest_commands=suggest_commands, 

1306 ) 

1307 if sub_group.name: 1acdefbg

1308 commands[sub_group.name] = sub_group 1acdefbg

1309 else: 

1310 if sub_group.callback: 1acdefbg

1311 import warnings 1acdefbg

1312 

1313 warnings.warn( 1acdefbg

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

1315 stacklevel=5, 

1316 ) 

1317 for sub_command_name, sub_command in sub_group.commands.items(): 1acdefbg

1318 commands[sub_command_name] = sub_command 1acdefbg

1319 solved_info = solve_typer_info_defaults(group_info) 1acdefbg

1320 ( 1acdefbg

1321 params, 

1322 convertors, 

1323 context_param_name, 

1324 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) 

1325 cls = solved_info.cls or TyperGroup 1acdefbg

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

1327 group = cls( 1acdefbg

1328 name=solved_info.name or "", 

1329 commands=commands, 

1330 invoke_without_command=solved_info.invoke_without_command, 

1331 no_args_is_help=solved_info.no_args_is_help, 

1332 subcommand_metavar=solved_info.subcommand_metavar, 

1333 chain=solved_info.chain, 

1334 result_callback=solved_info.result_callback, 

1335 context_settings=solved_info.context_settings, 

1336 callback=get_callback( 

1337 callback=solved_info.callback, 

1338 params=params, 

1339 convertors=convertors, 

1340 context_param_name=context_param_name, 

1341 pretty_exceptions_short=pretty_exceptions_short, 

1342 ), 

1343 params=params, 

1344 help=solved_info.help, 

1345 epilog=solved_info.epilog, 

1346 short_help=solved_info.short_help, 

1347 options_metavar=solved_info.options_metavar, 

1348 add_help_option=solved_info.add_help_option, 

1349 hidden=solved_info.hidden, 

1350 deprecated=solved_info.deprecated, 

1351 rich_markup_mode=rich_markup_mode, 

1352 # Rich settings 

1353 rich_help_panel=solved_info.rich_help_panel, 

1354 suggest_commands=suggest_commands, 

1355 ) 

1356 return group 1acdefbg

1357 

1358 

1359def get_command_name(name: str) -> str: 1acdefbg

1360 return name.lower().replace("_", "-") 1acdefbg

1361 

1362 

1363def get_params_convertors_ctx_param_name_from_function( 1acdefbg

1364 callback: Callable[..., Any] | None, 

1365) -> tuple[list[click.Argument | click.Option], dict[str, Any], str | None]: 

1366 params = [] 1acdefbg

1367 convertors = {} 1acdefbg

1368 context_param_name = None 1acdefbg

1369 if callback: 1acdefbg

1370 parameters = get_params_from_function(callback) 1acdefbg

1371 for param_name, param in parameters.items(): 1acdefbg

1372 if lenient_issubclass(param.annotation, click.Context): 1acdefbg

1373 context_param_name = param_name 1acdefbg

1374 continue 1acdefbg

1375 click_param, convertor = get_click_param(param) 1acdefbg

1376 if convertor: 1acdefbg

1377 convertors[param_name] = convertor 1acdefbg

1378 params.append(click_param) 1acdefbg

1379 return params, convertors, context_param_name 1acdefbg

1380 

1381 

1382def get_command_from_info( 1acdefbg

1383 command_info: CommandInfo, 

1384 *, 

1385 pretty_exceptions_short: bool, 

1386 rich_markup_mode: MarkupMode, 

1387) -> click.Command: 

1388 assert command_info.callback, "A command must have a callback function" 1acdefbg

1389 name = command_info.name or get_command_name(command_info.callback.__name__) # ty: ignore 1acdefbg

1390 use_help = command_info.help 1acdefbg

1391 if use_help is None: 1acdefbg

1392 use_help = inspect.getdoc(command_info.callback) 1acdefbg

1393 else: 

1394 use_help = inspect.cleandoc(use_help) 1acdefbg

1395 ( 1acdefbg

1396 params, 

1397 convertors, 

1398 context_param_name, 

1399 ) = get_params_convertors_ctx_param_name_from_function(command_info.callback) 

1400 cls = command_info.cls or TyperCommand 1acdefbg

1401 command = cls( 1acdefbg

1402 name=name, 

1403 context_settings=command_info.context_settings, 

1404 callback=get_callback( 

1405 callback=command_info.callback, 

1406 params=params, 

1407 convertors=convertors, 

1408 context_param_name=context_param_name, 

1409 pretty_exceptions_short=pretty_exceptions_short, 

1410 ), 

1411 params=params, # type: ignore 

1412 help=use_help, 

1413 epilog=command_info.epilog, 

1414 short_help=command_info.short_help, 

1415 options_metavar=command_info.options_metavar, 

1416 add_help_option=command_info.add_help_option, 

1417 no_args_is_help=command_info.no_args_is_help, 

1418 hidden=command_info.hidden, 

1419 deprecated=command_info.deprecated, 

1420 rich_markup_mode=rich_markup_mode, 

1421 # Rich settings 

1422 rich_help_panel=command_info.rich_help_panel, 

1423 ) 

1424 return command 1acdefbg

1425 

1426 

1427def determine_type_convertor(type_: Any) -> Callable[[Any], Any] | None: 1acdefbg

1428 convertor: Callable[[Any], Any] | None = None 1acdefbg

1429 if lenient_issubclass(type_, Path): 1acdefbg

1430 convertor = param_path_convertor 1acdefbg

1431 if lenient_issubclass(type_, Enum): 1acdefbg

1432 convertor = generate_enum_convertor(type_) 1acdefbg

1433 return convertor 1acdefbg

1434 

1435 

1436def param_path_convertor(value: str | None = None) -> Path | None: 1acdefbg

1437 if value is not None: 1acdefbg

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

1439 # it back to a Path 

1440 return value if isinstance(value, Path) else Path(value) 1acdefbg

1441 return None 1acdefbg

1442 

1443 

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

1445 val_map = {str(val.value): val for val in enum} 1acdefbg

1446 

1447 def convertor(value: Any) -> Any: 1acdefbg

1448 if value is not None: 1acdefbg

1449 val = str(value) 1acdefbg

1450 if val in val_map: 1acdefbg

1451 key = val_map[val] 1acdefbg

1452 return enum(key) 1acdefbg

1453 

1454 return convertor 1acdefbg

1455 

1456 

1457def generate_list_convertor( 1acdefbg

1458 convertor: Callable[[Any], Any] | None, default_value: Any | None 

1459) -> Callable[[Sequence[Any] | None], list[Any] | None]: 

1460 def internal_convertor(value: Sequence[Any] | None) -> list[Any] | None: 1acdefbg

1461 if (value is None) or (default_value is None and len(value) == 0): 1acdefbg

1462 return None 1acdefbg

1463 return [convertor(v) if convertor else v for v in value] 1acdefbg

1464 

1465 return internal_convertor 1acdefbg

1466 

1467 

1468def generate_tuple_convertor( 1acdefbg

1469 types: Sequence[Any], 

1470) -> Callable[[tuple[Any, ...] | None], tuple[Any, ...] | None]: 

1471 convertors = [determine_type_convertor(type_) for type_ in types] 1acdefbg

1472 

1473 def internal_convertor( 1acdefbg

1474 param_args: tuple[Any, ...] | None, 

1475 ) -> tuple[Any, ...] | None: 

1476 if param_args is None: 1acdefbg

1477 return None 1acdefbg

1478 return tuple( 1acdefbg

1479 convertor(arg) if convertor else arg 

1480 for (convertor, arg) in zip(convertors, param_args, strict=False) 

1481 ) 

1482 

1483 return internal_convertor 1acdefbg

1484 

1485 

1486def get_callback( 1acdefbg

1487 *, 

1488 callback: Callable[..., Any] | None = None, 

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

1490 convertors: dict[str, Callable[[str], Any]] | None = None, 

1491 context_param_name: str | None = None, 

1492 pretty_exceptions_short: bool, 

1493) -> Callable[..., Any] | None: 

1494 use_convertors = convertors or {} 1acdefbg

1495 if not callback: 1acdefbg

1496 return None 1acdefbg

1497 parameters = get_params_from_function(callback) 1acdefbg

1498 use_params: dict[str, Any] = {} 1acdefbg

1499 for param_name in parameters: 1acdefbg

1500 use_params[param_name] = None 1acdefbg

1501 for param in params: 1acdefbg

1502 if param.name: 1acdefbg

1503 use_params[param.name] = param.default 1acdefbg

1504 

1505 def wrapper(**kwargs: Any) -> Any: 1acdefbg

1506 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1acdefbg

1507 for k, v in kwargs.items(): 1acdefbg

1508 if k in use_convertors: 1acdefbg

1509 use_params[k] = use_convertors[k](v) 1acdefbg

1510 else: 

1511 use_params[k] = v 1acdefbg

1512 if context_param_name: 1acdefbg

1513 use_params[context_param_name] = click.get_current_context() 1acdefbg

1514 return callback(**use_params) 1acdefbg

1515 

1516 update_wrapper(wrapper, callback) 1acdefbg

1517 return wrapper 1acdefbg

1518 

1519 

1520def get_click_type( 1acdefbg

1521 *, annotation: Any, parameter_info: ParameterInfo 

1522) -> click.ParamType: 

1523 if parameter_info.click_type is not None: 1acdefbg

1524 return parameter_info.click_type 1acdefbg

1525 

1526 elif parameter_info.parser is not None: 1acdefbg

1527 return click.types.FuncParamType(parameter_info.parser) 1acdefbg

1528 

1529 elif annotation is str: 1acdefbg

1530 return click.STRING 1acdefbg

1531 elif annotation is int: 1acdefbg

1532 if parameter_info.min is not None or parameter_info.max is not None: 1acdefbg

1533 min_ = None 1acdefbg

1534 max_ = None 1acdefbg

1535 if parameter_info.min is not None: 1acdefbg

1536 min_ = int(parameter_info.min) 1acdefbg

1537 if parameter_info.max is not None: 1acdefbg

1538 max_ = int(parameter_info.max) 1acdefbg

1539 return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1acdefbg

1540 else: 

1541 return click.INT 1acdefbg

1542 elif annotation is float: 1acdefbg

1543 if parameter_info.min is not None or parameter_info.max is not None: 1acdefbg

1544 return click.FloatRange( 1acdefbg

1545 min=parameter_info.min, 

1546 max=parameter_info.max, 

1547 clamp=parameter_info.clamp, 

1548 ) 

1549 else: 

1550 return click.FLOAT 1acdefbg

1551 elif annotation is bool: 1acdefbg

1552 return click.BOOL 1acdefbg

1553 elif annotation == UUID: 1acdefbg

1554 return click.UUID 1acdefbg

1555 elif annotation == datetime: 1acdefbg

1556 return click.DateTime(formats=parameter_info.formats) 1acdefbg

1557 elif ( 

1558 annotation == Path 

1559 or parameter_info.allow_dash 

1560 or parameter_info.path_type 

1561 or parameter_info.resolve_path 

1562 ): 

1563 return TyperPath( 1acdefbg

1564 exists=parameter_info.exists, 

1565 file_okay=parameter_info.file_okay, 

1566 dir_okay=parameter_info.dir_okay, 

1567 writable=parameter_info.writable, 

1568 readable=parameter_info.readable, 

1569 resolve_path=parameter_info.resolve_path, 

1570 allow_dash=parameter_info.allow_dash, 

1571 path_type=parameter_info.path_type, 

1572 ) 

1573 elif lenient_issubclass(annotation, FileTextWrite): 1acdefbg

1574 return click.File( 1acdefbg

1575 mode=parameter_info.mode or "w", 

1576 encoding=parameter_info.encoding, 

1577 errors=parameter_info.errors, 

1578 lazy=parameter_info.lazy, 

1579 atomic=parameter_info.atomic, 

1580 ) 

1581 elif lenient_issubclass(annotation, FileText): 1acdefbg

1582 return click.File( 1acdefbg

1583 mode=parameter_info.mode or "r", 

1584 encoding=parameter_info.encoding, 

1585 errors=parameter_info.errors, 

1586 lazy=parameter_info.lazy, 

1587 atomic=parameter_info.atomic, 

1588 ) 

1589 elif lenient_issubclass(annotation, FileBinaryRead): 1acdefbg

1590 return click.File( 1acdefbg

1591 mode=parameter_info.mode or "rb", 

1592 encoding=parameter_info.encoding, 

1593 errors=parameter_info.errors, 

1594 lazy=parameter_info.lazy, 

1595 atomic=parameter_info.atomic, 

1596 ) 

1597 elif lenient_issubclass(annotation, FileBinaryWrite): 1acdefbg

1598 return click.File( 1acdefbg

1599 mode=parameter_info.mode or "wb", 

1600 encoding=parameter_info.encoding, 

1601 errors=parameter_info.errors, 

1602 lazy=parameter_info.lazy, 

1603 atomic=parameter_info.atomic, 

1604 ) 

1605 elif lenient_issubclass(annotation, Enum): 1acdefbg

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

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

1608 # support for enum values but reading enum names. 

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

1610 # Click < 8.2.0. 

1611 return TyperChoice( 1acdefbg

1612 [item.value for item in annotation], 

1613 case_sensitive=parameter_info.case_sensitive, 

1614 ) 

1615 elif is_literal_type(annotation): 1acdefbg

1616 return click.Choice( 1acdefbg

1617 literal_values(annotation), 

1618 case_sensitive=parameter_info.case_sensitive, 

1619 ) 

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

1621 

1622 

1623def lenient_issubclass(cls: Any, class_or_tuple: AnyType | tuple[AnyType, ...]) -> bool: 1acdefbg

1624 return isinstance(cls, type) and issubclass(cls, class_or_tuple) 1acdefbg

1625 

1626 

1627def get_click_param( 1acdefbg

1628 param: ParamMeta, 

1629) -> tuple[click.Argument | click.Option, Any]: 

1630 # First, find out what will be: 

1631 # * ParamInfo (ArgumentInfo or OptionInfo) 

1632 # * default_value 

1633 # * required 

1634 default_value = None 1acdefbg

1635 required = False 1acdefbg

1636 if isinstance(param.default, ParameterInfo): 1acdefbg

1637 parameter_info = param.default 1acdefbg

1638 if parameter_info.default == Required: 1acdefbg

1639 required = True 1acdefbg

1640 else: 

1641 default_value = parameter_info.default 1acdefbg

1642 elif param.default == Required or param.default is param.empty: 1acdefbg

1643 required = True 1acdefbg

1644 parameter_info = ArgumentInfo() 1acdefbg

1645 else: 

1646 default_value = param.default 1acdefbg

1647 parameter_info = OptionInfo() 1acdefbg

1648 annotation: Any 

1649 if param.annotation is not param.empty: 1acdefbg

1650 annotation = param.annotation 1acdefbg

1651 else: 

1652 annotation = str 1acdefbg

1653 main_type = annotation 1acdefbg

1654 is_list = False 1acdefbg

1655 is_tuple = False 1acdefbg

1656 parameter_type: Any = None 1acdefbg

1657 is_flag = None 1acdefbg

1658 origin = get_origin(main_type) 1acdefbg

1659 

1660 if origin is not None: 1acdefbg

1661 # Handle SomeType | None and Optional[SomeType] 

1662 if is_union(origin): 1acdefbg

1663 types = [] 1acdefbg

1664 for type_ in get_args(main_type): 1acdefbg

1665 if type_ is NoneType: 1acdefbg

1666 continue 1acdefbg

1667 types.append(type_) 1acdefbg

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

1669 main_type = types[0] 1acdefbg

1670 origin = get_origin(main_type) 1acdefbg

1671 # Handle Tuples and Lists 

1672 if lenient_issubclass(origin, list): 1acdefbg

1673 main_type = get_args(main_type)[0] 1acdefbg

1674 assert not get_origin(main_type), ( 1acdefbg

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

1676 ) 

1677 is_list = True 1acdefbg

1678 elif lenient_issubclass(origin, tuple): 1acdefbg

1679 types = [] 1acdefbg

1680 for type_ in get_args(main_type): 1acdefbg

1681 assert not get_origin(type_), ( 1acdefbg

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

1683 ) 

1684 types.append( 1acdefbg

1685 get_click_type(annotation=type_, parameter_info=parameter_info) 

1686 ) 

1687 parameter_type = tuple(types) 1acdefbg

1688 is_tuple = True 1acdefbg

1689 if parameter_type is None: 1acdefbg

1690 parameter_type = get_click_type( 1acdefbg

1691 annotation=main_type, parameter_info=parameter_info 

1692 ) 

1693 convertor = determine_type_convertor(main_type) 1acdefbg

1694 if is_list: 1acdefbg

1695 convertor = generate_list_convertor( 1acdefbg

1696 convertor=convertor, default_value=default_value 

1697 ) 

1698 if is_tuple: 1acdefbg

1699 convertor = generate_tuple_convertor(get_args(main_type)) 1acdefbg

1700 if isinstance(parameter_info, OptionInfo): 1acdefbg

1701 if main_type is bool: 1acdefbg

1702 is_flag = True 1acdefbg

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

1704 # to bool internally 

1705 parameter_type = None 1acdefbg

1706 default_option_name = get_command_name(param.name) 1acdefbg

1707 if is_flag: 1acdefbg

1708 default_option_declaration = ( 1acdefbg

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

1710 ) 

1711 else: 

1712 default_option_declaration = f"--{default_option_name}" 1acdefbg

1713 param_decls = [param.name] 1acdefbg

1714 if parameter_info.param_decls: 1acdefbg

1715 param_decls.extend(parameter_info.param_decls) 1acdefbg

1716 else: 

1717 param_decls.append(default_option_declaration) 1acdefbg

1718 return ( 1acdefbg

1719 TyperOption( 

1720 # Option 

1721 param_decls=param_decls, 

1722 show_default=parameter_info.show_default, 

1723 prompt=parameter_info.prompt, 

1724 confirmation_prompt=parameter_info.confirmation_prompt, 

1725 prompt_required=parameter_info.prompt_required, 

1726 hide_input=parameter_info.hide_input, 

1727 is_flag=is_flag, 

1728 multiple=is_list, 

1729 count=parameter_info.count, 

1730 allow_from_autoenv=parameter_info.allow_from_autoenv, 

1731 type=parameter_type, 

1732 help=parameter_info.help, 

1733 hidden=parameter_info.hidden, 

1734 show_choices=parameter_info.show_choices, 

1735 show_envvar=parameter_info.show_envvar, 

1736 # Parameter 

1737 required=required, 

1738 default=default_value, 

1739 callback=get_param_callback( 

1740 callback=parameter_info.callback, convertor=convertor 

1741 ), 

1742 metavar=parameter_info.metavar, 

1743 expose_value=parameter_info.expose_value, 

1744 is_eager=parameter_info.is_eager, 

1745 envvar=parameter_info.envvar, 

1746 shell_complete=parameter_info.shell_complete, 

1747 autocompletion=get_param_completion(parameter_info.autocompletion), 

1748 # Rich settings 

1749 rich_help_panel=parameter_info.rich_help_panel, 

1750 ), 

1751 convertor, 

1752 ) 

1753 elif isinstance(parameter_info, ArgumentInfo): 1acdefbg

1754 param_decls = [param.name] 1acdefbg

1755 nargs = None 1acdefbg

1756 if is_list: 1acdefbg

1757 nargs = -1 1acdefbg

1758 return ( 1acdefbg

1759 TyperArgument( 

1760 # Argument 

1761 param_decls=param_decls, 

1762 type=parameter_type, 

1763 required=required, 

1764 nargs=nargs, 

1765 # TyperArgument 

1766 show_default=parameter_info.show_default, 

1767 show_choices=parameter_info.show_choices, 

1768 show_envvar=parameter_info.show_envvar, 

1769 help=parameter_info.help, 

1770 hidden=parameter_info.hidden, 

1771 # Parameter 

1772 default=default_value, 

1773 callback=get_param_callback( 

1774 callback=parameter_info.callback, convertor=convertor 

1775 ), 

1776 metavar=parameter_info.metavar, 

1777 expose_value=parameter_info.expose_value, 

1778 is_eager=parameter_info.is_eager, 

1779 envvar=parameter_info.envvar, 

1780 shell_complete=parameter_info.shell_complete, 

1781 autocompletion=get_param_completion(parameter_info.autocompletion), 

1782 # Rich settings 

1783 rich_help_panel=parameter_info.rich_help_panel, 

1784 ), 

1785 convertor, 

1786 ) 

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

1788 

1789 

1790def get_param_callback( 1acdefbg

1791 *, 

1792 callback: Callable[..., Any] | None = None, 

1793 convertor: Callable[..., Any] | None = None, 

1794) -> Callable[..., Any] | None: 

1795 if not callback: 1acdefbg

1796 return None 1acdefbg

1797 parameters = get_params_from_function(callback) 1acdefbg

1798 ctx_name = None 1acdefbg

1799 click_param_name = None 1acdefbg

1800 value_name = None 1acdefbg

1801 untyped_names: list[str] = [] 1acdefbg

1802 for param_name, param_sig in parameters.items(): 1acdefbg

1803 if lenient_issubclass(param_sig.annotation, click.Context): 1acdefbg

1804 ctx_name = param_name 1acdefbg

1805 elif lenient_issubclass(param_sig.annotation, click.Parameter): 1acdefbg

1806 click_param_name = param_name 1acdefbg

1807 else: 

1808 untyped_names.append(param_name) 1acdefbg

1809 # Extract value param name first 

1810 if untyped_names: 1acdefbg

1811 value_name = untyped_names.pop() 1acdefbg

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

1813 if untyped_names: 1acdefbg

1814 if ctx_name is None: 1acdefbg

1815 ctx_name = untyped_names.pop(0) 1acdefbg

1816 if click_param_name is None: 1acdefbg

1817 if untyped_names: 1acdefbg

1818 click_param_name = untyped_names.pop(0) 1acdefbg

1819 if untyped_names: 1acdefbg

1820 raise click.ClickException( 1acdefbg

1821 "Too many CLI parameter callback function parameters" 

1822 ) 

1823 

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

1825 use_params: dict[str, Any] = {} 1acdefbg

1826 if ctx_name: 1acdefbg

1827 use_params[ctx_name] = ctx 1acdefbg

1828 if click_param_name: 1acdefbg

1829 use_params[click_param_name] = param 1acdefbg

1830 if value_name: 1acdefbg

1831 if convertor: 1acdefbg

1832 use_value = convertor(value) 1acdefbg

1833 else: 

1834 use_value = value 1acdefbg

1835 use_params[value_name] = use_value 1acdefbg

1836 return callback(**use_params) 1acdefbg

1837 

1838 update_wrapper(wrapper, callback) 1acdefbg

1839 return wrapper 1acdefbg

1840 

1841 

1842def get_param_completion( 1acdefbg

1843 callback: Callable[..., Any] | None = None, 

1844) -> Callable[..., Any] | None: 

1845 if not callback: 1acdefbg

1846 return None 1acdefbg

1847 parameters = get_params_from_function(callback) 1acdefbg

1848 ctx_name = None 1acdefbg

1849 args_name = None 1acdefbg

1850 incomplete_name = None 1acdefbg

1851 unassigned_params = list(parameters.values()) 1acdefbg

1852 for param_sig in unassigned_params[:]: 1acdefbg

1853 origin = get_origin(param_sig.annotation) 1acdefbg

1854 if lenient_issubclass(param_sig.annotation, click.Context): 1acdefbg

1855 ctx_name = param_sig.name 1acdefbg

1856 unassigned_params.remove(param_sig) 1acdefbg

1857 elif lenient_issubclass(origin, list): 1acdefbg

1858 args_name = param_sig.name 1acdefbg

1859 unassigned_params.remove(param_sig) 1acdefbg

1860 elif lenient_issubclass(param_sig.annotation, str): 1acdefbg

1861 incomplete_name = param_sig.name 1acdefbg

1862 unassigned_params.remove(param_sig) 1acdefbg

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

1864 for param_sig in unassigned_params[:]: 1acdefbg

1865 if ctx_name is None and param_sig.name == "ctx": 1acdefbg

1866 ctx_name = param_sig.name 1acdefbg

1867 unassigned_params.remove(param_sig) 1acdefbg

1868 elif args_name is None and param_sig.name == "args": 1acdefbg

1869 args_name = param_sig.name 1acdefbg

1870 unassigned_params.remove(param_sig) 1acdefbg

1871 elif incomplete_name is None and param_sig.name == "incomplete": 1acdefbg

1872 incomplete_name = param_sig.name 1acdefbg

1873 unassigned_params.remove(param_sig) 1acdefbg

1874 # Extract value param name first 

1875 if unassigned_params: 1acdefbg

1876 show_params = " ".join([param.name for param in unassigned_params]) 1acdefbg

1877 raise click.ClickException( 1acdefbg

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

1879 ) 

1880 

1881 def wrapper(ctx: click.Context, args: list[str], incomplete: str | None) -> Any: 1acdefbg

1882 use_params: dict[str, Any] = {} 1acdefbg

1883 if ctx_name: 1acdefbg

1884 use_params[ctx_name] = ctx 1acdefbg

1885 if args_name: 1acdefbg

1886 use_params[args_name] = args 1acdefbg

1887 if incomplete_name: 1acdefbg

1888 use_params[incomplete_name] = incomplete 1acdefbg

1889 return callback(**use_params) 1acdefbg

1890 

1891 update_wrapper(wrapper, callback) 1acdefbg

1892 return wrapper 1acdefbg

1893 

1894 

1895def run( 1acdefbg

1896 function: Annotated[ 

1897 Callable[..., Any], 

1898 Doc( 

1899 """ 

1900 The function that should power this CLI application. 

1901 """ 

1902 ), 

1903 ], 

1904) -> None: 

1905 """ 

1906 This function converts a given function to a CLI application with `Typer()` and executes it. 

1907 

1908 ## Example 

1909 

1910 ```python 

1911 import typer 

1912 

1913 def main(name: str): 

1914 print(f"Hello {name}") 

1915 

1916 if __name__ == "__main__": 

1917 typer.run(main) 

1918 ``` 

1919 """ 

1920 app = Typer(add_completion=False) 1acdefbg

1921 app.command()(function) 1acdefbg

1922 app() 1acdefbg

1923 

1924 

1925def _is_macos() -> bool: 1acdefbg

1926 return platform.system() == "Darwin" 1acdefbg

1927 

1928 

1929def _is_linux_or_bsd() -> bool: 1acdefbg

1930 if platform.system() == "Linux": 1acdefbg

1931 return True 1acdefbg

1932 

1933 return "BSD" in platform.system() 1acdefbg

1934 

1935 

1936def launch( 1acdefbg

1937 url: Annotated[ 

1938 str, 

1939 Doc( 

1940 """ 

1941 URL or filename of the thing to launch. 

1942 """ 

1943 ), 

1944 ], 

1945 wait: Annotated[ 

1946 bool, 

1947 Doc( 

1948 """ 

1949 Wait for the program to exit before returning. This only works if the launched program blocks. 

1950 In particular, `xdg-open` on Linux does not block. 

1951 """ 

1952 ), 

1953 ] = False, 

1954 locate: Annotated[ 

1955 bool, 

1956 Doc( 

1957 """ 

1958 If this is set to `True`, then instead of launching the application associated with the URL, it will attempt to 

1959 launch a file manager with the file located. This might have weird effects if the URL does not point to the filesystem. 

1960 """ 

1961 ), 

1962 ] = False, 

1963) -> int: 

1964 """ 

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

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

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

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

1969 success. 

1970 

1971 This function handles url in different operating systems separately: 

1972 - On macOS (Darwin), it uses the `open` command. 

1973 - On Linux and BSD, it uses `xdg-open` if available. 

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

1975 

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

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

1978 

1979 ## Examples 

1980 ```python 

1981 import typer 

1982 

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

1984 ``` 

1985 

1986 ```python 

1987 import typer 

1988 

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

1990 ``` 

1991 """ 

1992 

1993 if url.startswith("http://") or url.startswith("https://"): 1acdefbg

1994 if _is_macos(): 1acdefbg

1995 return subprocess.Popen( 1acdefbg

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

1997 ).wait() 

1998 

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

2000 

2001 if has_xdg_open: 1acdefbg

2002 return subprocess.Popen( 1acdefbg

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

2004 ).wait() 

2005 

2006 import webbrowser 1acdefbg

2007 

2008 webbrowser.open(url) 1acdefbg

2009 

2010 return 0 1acdefbg

2011 

2012 else: 

2013 return click.launch(url) 1acdefbg