Coverage for typer / main.py: 100%

497 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-09 00:15 +0000

1import inspect 1acdebfg

2import os 1acdebfg

3import platform 1acdebfg

4import shutil 1acdebfg

5import subprocess 1acdebfg

6import sys 1acdebfg

7import traceback 1acdebfg

8from collections.abc import Callable, Sequence 1acdebfg

9from datetime import datetime 1acdebfg

10from enum import Enum 1acdebfg

11from functools import update_wrapper 1acdebfg

12from pathlib import Path 1acdebfg

13from traceback import FrameSummary, StackSummary 1acdebfg

14from types import TracebackType 1acdebfg

15from typing import Annotated, Any 1acdebfg

16from uuid import UUID 1acdebfg

17 

18from annotated_doc import Doc 1acdebfg

19from typer._types import TyperChoice 1acdebfg

20 

21from . import _click 1acdebfg

22from ._click import types 1acdebfg

23from ._click.globals import get_current_context 1acdebfg

24from ._typing import get_args, get_origin, is_literal_type, is_union, literal_values 1acdebfg

25from .completion import get_completion_inspect_parameters 1acdebfg

26from .core import ( 1acdebfg

27 DEFAULT_MARKUP_MODE, 

28 HAS_RICH, 

29 MarkupMode, 

30 TyperArgument, 

31 TyperCommand, 

32 TyperGroup, 

33 TyperOption, 

34) 

35from .models import ( 1acdebfg

36 AnyType, 

37 ArgumentInfo, 

38 CommandFunctionType, 

39 CommandInfo, 

40 Default, 

41 DefaultPlaceholder, 

42 DeveloperExceptionConfig, 

43 FileBinaryRead, 

44 FileBinaryWrite, 

45 FileText, 

46 FileTextWrite, 

47 NoneType, 

48 OptionInfo, 

49 ParameterInfo, 

50 ParamMeta, 

51 Required, 

52 TyperInfo, 

53 TyperPath, 

54) 

55from .utils import get_params_from_function 1acdebfg

56 

57_original_except_hook = sys.excepthook 1acdebfg

58_typer_developer_exception_attr_name = "__typer_developer_exception__" 1acdebfg

59 

60 

61def except_hook( 1acdebfg

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

63) -> None: 

64 exception_config: DeveloperExceptionConfig | None = getattr( 1acdebfg

65 exc_value, _typer_developer_exception_attr_name, None 

66 ) 

67 standard_traceback = os.getenv( 1acdebfg

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

69 ) 

70 if ( 1ab

71 standard_traceback 

72 or not exception_config 

73 or not exception_config.pretty_exceptions_enable 

74 ): 

75 _original_except_hook(exc_type, exc_value, tb) 1acdebfg

76 return 1acdebfg

77 typer_path = os.path.dirname(__file__) 1acdebfg

78 click_path = os.path.dirname(_click.__file__) 1acdebfg

79 internal_dir_names = [typer_path, click_path] 1acdebfg

80 exc = exc_value 1acdebfg

81 if HAS_RICH: 1acdebfg

82 from . import rich_utils 1acdebfg

83 

84 rich_tb = rich_utils.get_traceback(exc, exception_config, internal_dir_names) 1acdebfg

85 console_stderr = rich_utils._get_rich_console(stderr=True) 1acdebfg

86 console_stderr.print(rich_tb) 1acdebfg

87 return 1acdebfg

88 tb_exc = traceback.TracebackException.from_exception(exc) 1acdebfg

89 stack: list[FrameSummary] = [] 1acdebfg

90 for frame in tb_exc.stack: 1acdebfg

91 if any(frame.filename.startswith(path) for path in internal_dir_names): 1acdebfg

92 if not exception_config.pretty_exceptions_short: 1acdebfg

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

94 stack.append( 1acdebfg

95 traceback.FrameSummary( 

96 filename=frame.filename, 

97 lineno=frame.lineno, 

98 name=frame.name, 

99 line="", 

100 ) 

101 ) 

102 else: 

103 stack.append(frame) 1acdebfg

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

105 final_stack_summary = StackSummary.from_list(stack) 1acdebfg

106 tb_exc.stack = final_stack_summary 1acdebfg

107 for line in tb_exc.format(): 1acdebfg

108 print(line, file=sys.stderr) 1acdebfg

109 return 1acdebfg

110 

111 

112def get_install_completion_arguments() -> tuple[_click.Parameter, _click.Parameter]: 1acdebfg

113 install_param, show_param = get_completion_inspect_parameters() 1acdebfg

114 click_install_param, _ = get_click_param(install_param) 1acdebfg

115 click_show_param, _ = get_click_param(show_param) 1acdebfg

116 return click_install_param, click_show_param 1acdebfg

117 

118 

119class Typer: 1acdebfg

120 """ 

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

122 

123 Read more in the 

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

125 

126 ## Example 

127 

128 ```python 

129 import typer 

130 

131 app = typer.Typer() 

132 ``` 

133 """ 

134 

135 def __init__( 1acdebfg

136 self, 

137 *, 

138 name: Annotated[ 

139 str | None, 

140 Doc( 

141 """ 

142 The name of this application. 

143 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=...)`. 

144 

145 **Example** 

146 

147 ```python 

148 import typer 

149 

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

151 ``` 

152 """ 

153 ), 

154 ] = Default(None), 

155 cls: Annotated[ 

156 type[TyperGroup] | None, 

157 Doc( 

158 """ 

159 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`. 

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

161 

162 **Example** 

163 

164 ```python 

165 import typer 

166 

167 app = typer.Typer(cls=TyperGroup) 

168 ``` 

169 """ 

170 ), 

171 ] = Default(None), 

172 invoke_without_command: Annotated[ 

173 bool, 

174 Doc( 

175 """ 

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

177 

178 **Example** 

179 

180 ```python 

181 import typer 

182 

183 app = typer.Typer(invoke_without_command=True) 

184 ``` 

185 """ 

186 ), 

187 ] = Default(False), 

188 no_args_is_help: Annotated[ 

189 bool, 

190 Doc( 

191 """ 

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

193 

194 **Example** 

195 

196 ```python 

197 import typer 

198 

199 app = typer.Typer(no_args_is_help=True) 

200 ``` 

201 """ 

202 ), 

203 ] = Default(False), 

204 subcommand_metavar: Annotated[ 

205 str | None, 

206 Doc( 

207 """ 

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

209 from Click and supported for compatibility. 

210 

211 --- 

212 

213 How to represent the subcommand argument in help. 

214 """ 

215 ), 

216 ] = Default(None), 

217 chain: Annotated[ 

218 bool, 

219 Doc( 

220 """ 

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

222 from Click and supported for compatibility. 

223 

224 --- 

225 

226 Allow passing more than one subcommand argument. 

227 """ 

228 ), 

229 ] = Default(False), 

230 result_callback: Annotated[ 

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

232 Doc( 

233 """ 

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

235 from Click and supported for compatibility. 

236 

237 --- 

238 

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

240 """ 

241 ), 

242 ] = Default(None), 

243 # Command 

244 context_settings: Annotated[ 

245 dict[Any, Any] | None, 

246 Doc( 

247 """ 

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

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

250 

251 **Example** 

252 

253 ```python 

254 import typer 

255 

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

257 ``` 

258 """ 

259 ), 

260 ] = Default(None), 

261 callback: Annotated[ 

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

263 Doc( 

264 """ 

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

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

267 

268 **Example** 

269 

270 ```python 

271 import typer 

272 

273 def callback(): 

274 print("Running a command") 

275 

276 app = typer.Typer(callback=callback) 

277 ``` 

278 """ 

279 ), 

280 ] = Default(None), 

281 help: Annotated[ 

282 str | None, 

283 Doc( 

284 """ 

285 Help text for the main Typer app. 

286 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, 

287 and which one takes priority. 

288 

289 **Example** 

290 

291 ```python 

292 import typer 

293 

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

295 ``` 

296 """ 

297 ), 

298 ] = Default(None), 

299 epilog: Annotated[ 

300 str | None, 

301 Doc( 

302 """ 

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

304 

305 **Example** 

306 

307 ```python 

308 import typer 

309 

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

311 ``` 

312 """ 

313 ), 

314 ] = Default(None), 

315 short_help: Annotated[ 

316 str | None, 

317 Doc( 

318 """ 

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

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

321 

322 **Example** 

323 

324 ```python 

325 import typer 

326 

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

328 ``` 

329 """ 

330 ), 

331 ] = Default(None), 

332 options_metavar: Annotated[ 

333 str, 

334 Doc( 

335 """ 

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

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

338 

339 **Example** 

340 

341 ```python 

342 import typer 

343 

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

345 ``` 

346 """ 

347 ), 

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

349 add_help_option: Annotated[ 

350 bool, 

351 Doc( 

352 """ 

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

354 from Click and supported for compatibility. 

355 

356 --- 

357 

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

359 """ 

360 ), 

361 ] = Default(True), 

362 hidden: Annotated[ 

363 bool, 

364 Doc( 

365 """ 

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

367 

368 **Example** 

369 

370 ```python 

371 import typer 

372 

373 app = typer.Typer(hidden=True) 

374 ``` 

375 """ 

376 ), 

377 ] = Default(False), 

378 deprecated: Annotated[ 

379 bool, 

380 Doc( 

381 """ 

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

383 

384 **Example** 

385 

386 ```python 

387 import typer 

388 

389 app = typer.Typer(deprecated=True) 

390 ``` 

391 """ 

392 ), 

393 ] = Default(False), 

394 add_completion: Annotated[ 

395 bool, 

396 Doc( 

397 """ 

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

399 Set to `True` by default. 

400 

401 **Example** 

402 

403 ```python 

404 import typer 

405 

406 app = typer.Typer(add_completion=False) 

407 ``` 

408 """ 

409 ), 

410 ] = True, 

411 # Rich settings 

412 rich_markup_mode: Annotated[ 

413 MarkupMode, 

414 Doc( 

415 """ 

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

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

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

419 

420 **Example** 

421 

422 ```python 

423 import typer 

424 

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

426 ``` 

427 """ 

428 ), 

429 ] = DEFAULT_MARKUP_MODE, 

430 rich_help_panel: Annotated[ 

431 str | None, 

432 Doc( 

433 """ 

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

435 

436 **Example** 

437 

438 ```python 

439 import typer 

440 

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

442 ``` 

443 """ 

444 ), 

445 ] = Default(None), 

446 suggest_commands: Annotated[ 

447 bool, 

448 Doc( 

449 """ 

450 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. 

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

452 

453 **Example** 

454 

455 ```python 

456 import typer 

457 

458 app = typer.Typer(suggest_commands=False) 

459 ``` 

460 """ 

461 ), 

462 ] = True, 

463 pretty_exceptions_enable: Annotated[ 

464 bool, 

465 Doc( 

466 """ 

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

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

469 

470 **Example** 

471 

472 ```python 

473 import typer 

474 

475 app = typer.Typer(pretty_exceptions_enable=False) 

476 ``` 

477 """ 

478 ), 

479 ] = True, 

480 pretty_exceptions_show_locals: Annotated[ 

481 bool, 

482 Doc( 

483 """ 

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

485 will be nicely printed. 

486 

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

488 

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

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

491 

492 **Example** 

493 

494 ```python 

495 import typer 

496 

497 app = typer.Typer(pretty_exceptions_show_locals=True) 

498 ``` 

499 """ 

500 ), 

501 ] = False, 

502 pretty_exceptions_short: Annotated[ 

503 bool, 

504 Doc( 

505 """ 

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

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

508 

509 **Example** 

510 

511 ```python 

512 import typer 

513 

514 app = typer.Typer(pretty_exceptions_short=False) 

515 ``` 

516 """ 

517 ), 

518 ] = True, 

519 ): 

520 self._add_completion = add_completion 1acdebfg

521 self.rich_markup_mode: MarkupMode = rich_markup_mode 1acdebfg

522 self.rich_help_panel = rich_help_panel 1acdebfg

523 self.suggest_commands = suggest_commands 1acdebfg

524 self.pretty_exceptions_enable = pretty_exceptions_enable 1acdebfg

525 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1acdebfg

526 self.pretty_exceptions_short = pretty_exceptions_short 1acdebfg

527 self.info = TyperInfo( 1acdebfg

528 name=name, 

529 cls=cls, 

530 invoke_without_command=invoke_without_command, 

531 no_args_is_help=no_args_is_help, 

532 subcommand_metavar=subcommand_metavar, 

533 chain=chain, 

534 result_callback=result_callback, 

535 context_settings=context_settings, 

536 callback=callback, 

537 help=help, 

538 epilog=epilog, 

539 short_help=short_help, 

540 options_metavar=options_metavar, 

541 add_help_option=add_help_option, 

542 hidden=hidden, 

543 deprecated=deprecated, 

544 ) 

545 self.registered_groups: list[TyperInfo] = [] 1acdebfg

546 self.registered_commands: list[CommandInfo] = [] 1acdebfg

547 self.registered_callback: TyperInfo | None = None 1acdebfg

548 

549 def callback( 1acdebfg

550 self, 

551 *, 

552 cls: Annotated[ 

553 type[TyperGroup] | None, 

554 Doc( 

555 """ 

556 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`. 

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

558 """ 

559 ), 

560 ] = Default(None), 

561 invoke_without_command: Annotated[ 

562 bool, 

563 Doc( 

564 """ 

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

566 """ 

567 ), 

568 ] = Default(False), 

569 no_args_is_help: Annotated[ 

570 bool, 

571 Doc( 

572 """ 

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

574 """ 

575 ), 

576 ] = Default(False), 

577 subcommand_metavar: Annotated[ 

578 str | None, 

579 Doc( 

580 """ 

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

582 from Click and supported for compatibility. 

583 

584 --- 

585 

586 How to represent the subcommand argument in help. 

587 """ 

588 ), 

589 ] = Default(None), 

590 chain: Annotated[ 

591 bool, 

592 Doc( 

593 """ 

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

595 from Click and supported for compatibility. 

596 

597 --- 

598 

599 Allow passing more than one subcommand argument. 

600 """ 

601 ), 

602 ] = Default(False), 

603 result_callback: Annotated[ 

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

605 Doc( 

606 """ 

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

608 from Click and supported for compatibility. 

609 

610 --- 

611 

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

613 """ 

614 ), 

615 ] = Default(None), 

616 # Command 

617 context_settings: Annotated[ 

618 dict[Any, Any] | None, 

619 Doc( 

620 """ 

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

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

623 """ 

624 ), 

625 ] = Default(None), 

626 help: Annotated[ 

627 str | None, 

628 Doc( 

629 """ 

630 Help text for the command. 

631 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, 

632 and which one takes priority. 

633 """ 

634 ), 

635 ] = Default(None), 

636 epilog: Annotated[ 

637 str | None, 

638 Doc( 

639 """ 

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

641 """ 

642 ), 

643 ] = Default(None), 

644 short_help: Annotated[ 

645 str | None, 

646 Doc( 

647 """ 

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

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

650 """ 

651 ), 

652 ] = Default(None), 

653 options_metavar: Annotated[ 

654 str | None, 

655 Doc( 

656 """ 

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

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

659 """ 

660 ), 

661 ] = Default(None), 

662 add_help_option: Annotated[ 

663 bool, 

664 Doc( 

665 """ 

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

667 from Click and supported for compatibility. 

668 

669 --- 

670 

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

672 """ 

673 ), 

674 ] = Default(True), 

675 hidden: Annotated[ 

676 bool, 

677 Doc( 

678 """ 

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

680 """ 

681 ), 

682 ] = Default(False), 

683 deprecated: Annotated[ 

684 bool, 

685 Doc( 

686 """ 

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

688 """ 

689 ), 

690 ] = Default(False), 

691 # Rich settings 

692 rich_help_panel: Annotated[ 

693 str | None, 

694 Doc( 

695 """ 

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

697 """ 

698 ), 

699 ] = Default(None), 

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

701 """ 

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

703 

704 Read more in the 

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

706 

707 ## Example 

708 

709 ```python 

710 import typer 

711 

712 app = typer.Typer() 

713 state = {"verbose": False} 

714 

715 @app.callback() 

716 def main(verbose: bool = False): 

717 if verbose: 

718 print("Will write verbose output") 

719 state["verbose"] = True 

720 

721 @app.command() 

722 def delete(username: str): 

723 # define subcommand 

724 ... 

725 ``` 

726 """ 

727 

728 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1acdebfg

729 self.registered_callback = TyperInfo( 1acdebfg

730 cls=cls, 

731 invoke_without_command=invoke_without_command, 

732 no_args_is_help=no_args_is_help, 

733 subcommand_metavar=subcommand_metavar, 

734 chain=chain, 

735 result_callback=result_callback, 

736 context_settings=context_settings, 

737 callback=f, 

738 help=help, 

739 epilog=epilog, 

740 short_help=short_help, 

741 options_metavar=( 

742 options_metavar or self._info_val_str("options_metavar") 

743 ), 

744 add_help_option=add_help_option, 

745 hidden=hidden, 

746 deprecated=deprecated, 

747 rich_help_panel=rich_help_panel, 

748 ) 

749 return f 1acdebfg

750 

751 return decorator 1acdebfg

752 

753 def command( 1acdebfg

754 self, 

755 name: Annotated[ 

756 str | None, 

757 Doc( 

758 """ 

759 The name of this command. 

760 """ 

761 ), 

762 ] = None, 

763 *, 

764 cls: Annotated[ 

765 type[TyperCommand] | None, 

766 Doc( 

767 """ 

768 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`. 

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

770 """ 

771 ), 

772 ] = None, 

773 context_settings: Annotated[ 

774 dict[Any, Any] | None, 

775 Doc( 

776 """ 

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

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

779 """ 

780 ), 

781 ] = None, 

782 help: Annotated[ 

783 str | None, 

784 Doc( 

785 """ 

786 Help text for the command. 

787 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, 

788 and which one takes priority. 

789 """ 

790 ), 

791 ] = None, 

792 epilog: Annotated[ 

793 str | None, 

794 Doc( 

795 """ 

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

797 """ 

798 ), 

799 ] = None, 

800 short_help: Annotated[ 

801 str | None, 

802 Doc( 

803 """ 

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

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

806 """ 

807 ), 

808 ] = None, 

809 options_metavar: Annotated[ 

810 str | None, 

811 Doc( 

812 """ 

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

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

815 """ 

816 ), 

817 ] = Default(None), 

818 add_help_option: Annotated[ 

819 bool, 

820 Doc( 

821 """ 

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

823 from Click and supported for compatibility. 

824 

825 --- 

826 

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

828 """ 

829 ), 

830 ] = True, 

831 no_args_is_help: Annotated[ 

832 bool, 

833 Doc( 

834 """ 

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

836 """ 

837 ), 

838 ] = False, 

839 hidden: Annotated[ 

840 bool, 

841 Doc( 

842 """ 

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

844 """ 

845 ), 

846 ] = False, 

847 deprecated: Annotated[ 

848 bool, 

849 Doc( 

850 """ 

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

852 """ 

853 ), 

854 ] = False, 

855 # Rich settings 

856 rich_help_panel: Annotated[ 

857 str | None, 

858 Doc( 

859 """ 

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

861 """ 

862 ), 

863 ] = Default(None), 

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

865 """ 

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

867 

868 Read more in the 

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

870 

871 ## Example 

872 

873 ```python 

874 import typer 

875 

876 app = typer.Typer() 

877 

878 @app.command() 

879 def create(): 

880 print("Creating user: Hiro Hamada") 

881 

882 @app.command() 

883 def delete(): 

884 print("Deleting user: Hiro Hamada") 

885 ``` 

886 """ 

887 if cls is None: 1acdebfg

888 cls = TyperCommand 1acdebfg

889 

890 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1acdebfg

891 self.registered_commands.append( 1acdebfg

892 CommandInfo( 

893 name=name, 

894 cls=cls, 

895 context_settings=context_settings, 

896 callback=f, 

897 help=help, 

898 epilog=epilog, 

899 short_help=short_help, 

900 options_metavar=( 

901 options_metavar or self._info_val_str("options_metavar") 

902 ), 

903 add_help_option=add_help_option, 

904 no_args_is_help=no_args_is_help, 

905 hidden=hidden, 

906 deprecated=deprecated, 

907 # Rich settings 

908 rich_help_panel=rich_help_panel, 

909 ) 

910 ) 

911 return f 1acdebfg

912 

913 return decorator 1acdebfg

914 

915 def add_typer( 1acdebfg

916 self, 

917 typer_instance: "Typer", 

918 *, 

919 name: Annotated[ 

920 str | None, 

921 Doc( 

922 """ 

923 The name of this subcommand. 

924 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, 

925 and which one takes priority. 

926 """ 

927 ), 

928 ] = Default(None), 

929 cls: Annotated[ 

930 type[TyperGroup] | None, 

931 Doc( 

932 """ 

933 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`. 

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

935 """ 

936 ), 

937 ] = Default(None), 

938 invoke_without_command: Annotated[ 

939 bool, 

940 Doc( 

941 """ 

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

943 """ 

944 ), 

945 ] = Default(False), 

946 no_args_is_help: Annotated[ 

947 bool, 

948 Doc( 

949 """ 

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

951 """ 

952 ), 

953 ] = Default(False), 

954 subcommand_metavar: Annotated[ 

955 str | None, 

956 Doc( 

957 """ 

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

959 from Click and supported for compatibility. 

960 

961 --- 

962 

963 How to represent the subcommand argument in help. 

964 """ 

965 ), 

966 ] = Default(None), 

967 chain: Annotated[ 

968 bool, 

969 Doc( 

970 """ 

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

972 from Click and supported for compatibility. 

973 

974 --- 

975 

976 Allow passing more than one subcommand argument. 

977 """ 

978 ), 

979 ] = Default(False), 

980 result_callback: Annotated[ 

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

982 Doc( 

983 """ 

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

985 from Click and supported for compatibility. 

986 

987 --- 

988 

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

990 """ 

991 ), 

992 ] = Default(None), 

993 # Command 

994 context_settings: Annotated[ 

995 dict[Any, Any] | None, 

996 Doc( 

997 """ 

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

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

1000 """ 

1001 ), 

1002 ] = Default(None), 

1003 callback: Annotated[ 

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

1005 Doc( 

1006 """ 

1007 Add a callback to this app. 

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

1009 """ 

1010 ), 

1011 ] = Default(None), 

1012 help: Annotated[ 

1013 str | None, 

1014 Doc( 

1015 """ 

1016 Help text for the subcommand. 

1017 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, 

1018 and which one takes priority. 

1019 """ 

1020 ), 

1021 ] = Default(None), 

1022 epilog: Annotated[ 

1023 str | None, 

1024 Doc( 

1025 """ 

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

1027 """ 

1028 ), 

1029 ] = Default(None), 

1030 short_help: Annotated[ 

1031 str | None, 

1032 Doc( 

1033 """ 

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

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

1036 """ 

1037 ), 

1038 ] = Default(None), 

1039 options_metavar: Annotated[ 

1040 str | None, 

1041 Doc( 

1042 """ 

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

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

1045 """ 

1046 ), 

1047 ] = Default(None), 

1048 add_help_option: Annotated[ 

1049 bool, 

1050 Doc( 

1051 """ 

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

1053 from Click and supported for compatibility. 

1054 

1055 --- 

1056 

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

1058 """ 

1059 ), 

1060 ] = Default(True), 

1061 hidden: Annotated[ 

1062 bool, 

1063 Doc( 

1064 """ 

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

1066 """ 

1067 ), 

1068 ] = Default(False), 

1069 deprecated: Annotated[ 

1070 bool, 

1071 Doc( 

1072 """ 

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

1074 """ 

1075 ), 

1076 ] = False, 

1077 # Rich settings 

1078 rich_help_panel: Annotated[ 

1079 str | None, 

1080 Doc( 

1081 """ 

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

1083 """ 

1084 ), 

1085 ] = Default(None), 

1086 ) -> None: 

1087 """ 

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

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

1090 

1091 Read more in the 

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

1093 

1094 ## Example 

1095 

1096 ```python 

1097 import typer 

1098 

1099 from .add import app as add_app 

1100 from .delete import app as delete_app 

1101 

1102 app = typer.Typer() 

1103 

1104 app.add_typer(add_app) 

1105 app.add_typer(delete_app) 

1106 ``` 

1107 """ 

1108 self.registered_groups.append( 1acdebfg

1109 TyperInfo( 

1110 typer_instance, 

1111 name=name, 

1112 cls=cls, 

1113 invoke_without_command=invoke_without_command, 

1114 no_args_is_help=no_args_is_help, 

1115 subcommand_metavar=subcommand_metavar, 

1116 chain=chain, 

1117 result_callback=result_callback, 

1118 context_settings=context_settings, 

1119 callback=callback, 

1120 help=help, 

1121 epilog=epilog, 

1122 short_help=short_help, 

1123 options_metavar=( 

1124 options_metavar or self._info_val_str("options_metavar") 

1125 ), 

1126 add_help_option=add_help_option, 

1127 hidden=hidden, 

1128 deprecated=deprecated, 

1129 rich_help_panel=rich_help_panel, 

1130 ) 

1131 ) 

1132 

1133 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1acdebfg

1134 if sys.excepthook != except_hook: 1acdebfg

1135 sys.excepthook = except_hook 1acdebfg

1136 try: 1acdebfg

1137 return get_command(self)(*args, **kwargs) 1acdebfg

1138 except Exception as e: 1acdebfg

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

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

1141 # raise custom_exc from e 

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

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

1144 # actual error last. 

1145 setattr( 1acdebfg

1146 e, 

1147 _typer_developer_exception_attr_name, 

1148 DeveloperExceptionConfig( 

1149 pretty_exceptions_enable=self.pretty_exceptions_enable, 

1150 pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, 

1151 pretty_exceptions_short=self.pretty_exceptions_short, 

1152 ), 

1153 ) 

1154 raise e 1acdebfg

1155 

1156 def _info_val_str(self, name: str) -> str: 1acdebfg

1157 val = getattr(self.info, name) 1acdebfg

1158 val_str = val.value if isinstance(val, DefaultPlaceholder) else val 1acdebfg

1159 assert isinstance(val_str, str) 1acdebfg

1160 return val_str 1acdebfg

1161 

1162 

1163def get_group(typer_instance: Typer) -> TyperGroup: 1acdebfg

1164 group = get_group_from_info( 1acdebfg

1165 TyperInfo(typer_instance), 

1166 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

1167 rich_markup_mode=typer_instance.rich_markup_mode, 

1168 suggest_commands=typer_instance.suggest_commands, 

1169 ) 

1170 return group 1acdebfg

1171 

1172 

1173def get_command(typer_instance: Typer) -> _click.Command: 1acdebfg

1174 if typer_instance._add_completion: 1acdebfg

1175 click_install_param, click_show_param = get_install_completion_arguments() 1acdebfg

1176 if ( 1ab

1177 typer_instance.registered_callback 

1178 or typer_instance.info.callback 

1179 or typer_instance.registered_groups 

1180 or len(typer_instance.registered_commands) > 1 

1181 ): 

1182 # Create a Group 

1183 click_command: _click.Command = get_group(typer_instance) 1acdebfg

1184 if typer_instance._add_completion: 1acdebfg

1185 click_command.params.append(click_install_param) 1acdebfg

1186 click_command.params.append(click_show_param) 1acdebfg

1187 return click_command 1acdebfg

1188 elif len(typer_instance.registered_commands) == 1: 1acdebfg

1189 # Create a single Command 

1190 single_command = typer_instance.registered_commands[0] 1acdebfg

1191 

1192 if not single_command.context_settings and not isinstance( 1acdebfg

1193 typer_instance.info.context_settings, DefaultPlaceholder 

1194 ): 

1195 single_command.context_settings = typer_instance.info.context_settings 1acdebfg

1196 

1197 click_command = get_command_from_info( 1acdebfg

1198 single_command, 

1199 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

1200 rich_markup_mode=typer_instance.rich_markup_mode, 

1201 ) 

1202 if typer_instance._add_completion: 1acdebfg

1203 click_command.params.append(click_install_param) 1acdebfg

1204 click_command.params.append(click_show_param) 1acdebfg

1205 return click_command 1acdebfg

1206 raise RuntimeError( 

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

1208 ) # pragma: no cover 

1209 

1210 

1211def solve_typer_info_help(typer_info: TyperInfo) -> str: 1acdebfg

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

1213 if not isinstance(typer_info.help, DefaultPlaceholder): 1acdebfg

1214 return inspect.cleandoc(typer_info.help or "") 1acdebfg

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

1216 if typer_info.typer_instance and typer_info.typer_instance.registered_callback: 1acdebfg

1217 callback_help = typer_info.typer_instance.registered_callback.help 1acdebfg

1218 if not isinstance(callback_help, DefaultPlaceholder): 1acdebfg

1219 return inspect.cleandoc(callback_help or "") 1acdebfg

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

1221 if typer_info.typer_instance and typer_info.typer_instance.info: 1acdebfg

1222 instance_help = typer_info.typer_instance.info.help 1acdebfg

1223 if not isinstance(instance_help, DefaultPlaceholder): 1acdebfg

1224 return inspect.cleandoc(instance_help or "") 1acdebfg

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

1226 if typer_info.callback: 1acdebfg

1227 doc = inspect.getdoc(typer_info.callback) 1acdebfg

1228 if doc: 1acdebfg

1229 return doc 1acdebfg

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

1231 if typer_info.typer_instance and typer_info.typer_instance.registered_callback: 1acdebfg

1232 callback = typer_info.typer_instance.registered_callback.callback 1acdebfg

1233 if not isinstance(callback, DefaultPlaceholder): 1acdebfg

1234 doc = inspect.getdoc(callback or "") 1acdebfg

1235 if doc: 1acdebfg

1236 return doc 1acdebfg

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

1238 if typer_info.typer_instance and typer_info.typer_instance.info: 1acdebfg

1239 instance_callback = typer_info.typer_instance.info.callback 1acdebfg

1240 if not isinstance(instance_callback, DefaultPlaceholder): 1acdebfg

1241 doc = inspect.getdoc(instance_callback) 1acdebfg

1242 if doc: 1acdebfg

1243 return doc 1acdebfg

1244 # Value not set, use the default 

1245 return typer_info.help.value 1acdebfg

1246 

1247 

1248def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1acdebfg

1249 values: dict[str, Any] = {} 1acdebfg

1250 for name, value in typer_info.__dict__.items(): 1acdebfg

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

1252 if not isinstance(value, DefaultPlaceholder): 1acdebfg

1253 values[name] = value 1acdebfg

1254 continue 1acdebfg

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

1256 try: 1acdebfg

1257 callback_value = getattr( 1acdebfg

1258 typer_info.typer_instance.registered_callback, # type: ignore 

1259 name, 

1260 ) 

1261 if not isinstance(callback_value, DefaultPlaceholder): 1acdebfg

1262 values[name] = callback_value 1acdebfg

1263 continue 1acdebfg

1264 except AttributeError: 1acdebfg

1265 pass 1acdebfg

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

1267 try: 1acdebfg

1268 instance_value = getattr( 1acdebfg

1269 typer_info.typer_instance.info, # type: ignore 

1270 name, 

1271 ) 

1272 if not isinstance(instance_value, DefaultPlaceholder): 1acdebfg

1273 values[name] = instance_value 1acdebfg

1274 continue 1acdebfg

1275 except AttributeError: 1acdebfg

1276 pass 1acdebfg

1277 # Value not set, use the default 

1278 values[name] = value.value 1acdebfg

1279 values["help"] = solve_typer_info_help(typer_info) 1acdebfg

1280 return TyperInfo(**values) 1acdebfg

1281 

1282 

1283def get_group_from_info( 1acdebfg

1284 group_info: TyperInfo, 

1285 *, 

1286 pretty_exceptions_short: bool, 

1287 suggest_commands: bool, 

1288 rich_markup_mode: MarkupMode, 

1289) -> TyperGroup: 

1290 assert group_info.typer_instance, ( 1acdebfg

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

1292 ) 

1293 commands: dict[str, _click.Command] = {} 1acdebfg

1294 for command_info in group_info.typer_instance.registered_commands: 1acdebfg

1295 command = get_command_from_info( 1acdebfg

1296 command_info=command_info, 

1297 pretty_exceptions_short=pretty_exceptions_short, 

1298 rich_markup_mode=rich_markup_mode, 

1299 ) 

1300 if command.name: 1acdebfg

1301 commands[command.name] = command 1acdebfg

1302 for sub_group_info in group_info.typer_instance.registered_groups: 1acdebfg

1303 sub_group = get_group_from_info( 1acdebfg

1304 sub_group_info, 

1305 pretty_exceptions_short=pretty_exceptions_short, 

1306 rich_markup_mode=rich_markup_mode, 

1307 suggest_commands=suggest_commands, 

1308 ) 

1309 if sub_group.name: 1acdebfg

1310 commands[sub_group.name] = sub_group 1acdebfg

1311 else: 

1312 if sub_group.callback: 1acdebfg

1313 import warnings 1acdebfg

1314 

1315 warnings.warn( 1acdebfg

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

1317 stacklevel=5, 

1318 ) 

1319 for sub_command_name, sub_command in sub_group.commands.items(): 1acdebfg

1320 commands[sub_command_name] = sub_command 1acdebfg

1321 solved_info = solve_typer_info_defaults(group_info) 1acdebfg

1322 ( 1acdebfg

1323 params, 

1324 convertors, 

1325 context_param_name, 

1326 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) 

1327 cls = solved_info.cls or TyperGroup 1acdebfg

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

1329 group = cls( 1acdebfg

1330 name=solved_info.name or "", 

1331 commands=commands, 

1332 invoke_without_command=solved_info.invoke_without_command, 

1333 no_args_is_help=solved_info.no_args_is_help, 

1334 subcommand_metavar=solved_info.subcommand_metavar, 

1335 result_callback=solved_info.result_callback, 

1336 context_settings=solved_info.context_settings, 

1337 callback=get_callback( 

1338 callback=solved_info.callback, 

1339 params=params, 

1340 convertors=convertors, 

1341 context_param_name=context_param_name, 

1342 pretty_exceptions_short=pretty_exceptions_short, 

1343 ), 

1344 params=params, 

1345 help=solved_info.help, 

1346 epilog=solved_info.epilog, 

1347 short_help=solved_info.short_help, 

1348 options_metavar=solved_info.options_metavar, 

1349 add_help_option=solved_info.add_help_option, 

1350 hidden=solved_info.hidden, 

1351 deprecated=solved_info.deprecated, 

1352 rich_markup_mode=rich_markup_mode, 

1353 # Rich settings 

1354 rich_help_panel=solved_info.rich_help_panel, 

1355 suggest_commands=suggest_commands, 

1356 ) 

1357 return group 1acdebfg

1358 

1359 

1360def get_command_name(name: str) -> str: 1acdebfg

1361 return name.lower().replace("_", "-") 1acdebfg

1362 

1363 

1364def get_default_option_flag_name(name: str) -> str: 1acdebfg

1365 return name.replace("_", "-") 1acdebfg

1366 

1367 

1368def get_params_convertors_ctx_param_name_from_function( 1acdebfg

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

1370) -> tuple[list[TyperArgument | TyperOption], dict[str, Any], str | None]: 

1371 params = [] 1acdebfg

1372 convertors = {} 1acdebfg

1373 context_param_name = None 1acdebfg

1374 if callback: 1acdebfg

1375 parameters = get_params_from_function(callback) 1acdebfg

1376 for param_name, param in parameters.items(): 1acdebfg

1377 if lenient_issubclass(param.annotation, _click.Context): 1acdebfg

1378 context_param_name = param_name 1acdebfg

1379 continue 1acdebfg

1380 click_param, convertor = get_click_param(param) 1acdebfg

1381 if convertor: 1acdebfg

1382 convertors[param_name] = convertor 1acdebfg

1383 params.append(click_param) 1acdebfg

1384 return params, convertors, context_param_name 1acdebfg

1385 

1386 

1387def get_command_from_info( 1acdebfg

1388 command_info: CommandInfo, 

1389 *, 

1390 pretty_exceptions_short: bool, 

1391 rich_markup_mode: MarkupMode, 

1392) -> _click.Command: 

1393 assert command_info.callback, "A command must have a callback function" 1acdebfg

1394 name = command_info.name or get_command_name(command_info.callback.__name__) # ty: ignore 1acdebfg

1395 use_help = command_info.help 1acdebfg

1396 if use_help is None: 1acdebfg

1397 use_help = inspect.getdoc(command_info.callback) 1acdebfg

1398 else: 

1399 use_help = inspect.cleandoc(use_help) 1acdebfg

1400 ( 1acdebfg

1401 params, 

1402 convertors, 

1403 context_param_name, 

1404 ) = get_params_convertors_ctx_param_name_from_function(command_info.callback) 

1405 cls = command_info.cls or TyperCommand 1acdebfg

1406 command = cls( 1acdebfg

1407 name=name, 

1408 context_settings=command_info.context_settings, 

1409 callback=get_callback( 

1410 callback=command_info.callback, 

1411 params=params, 

1412 convertors=convertors, 

1413 context_param_name=context_param_name, 

1414 pretty_exceptions_short=pretty_exceptions_short, 

1415 ), 

1416 params=params, # type: ignore 

1417 help=use_help, 

1418 epilog=command_info.epilog, 

1419 short_help=command_info.short_help, 

1420 options_metavar=command_info.options_metavar, 

1421 add_help_option=command_info.add_help_option, 

1422 no_args_is_help=command_info.no_args_is_help, 

1423 hidden=command_info.hidden, 

1424 deprecated=command_info.deprecated, 

1425 rich_markup_mode=rich_markup_mode, 

1426 # Rich settings 

1427 rich_help_panel=command_info.rich_help_panel, 

1428 ) 

1429 return command 1acdebfg

1430 

1431 

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

1433 convertor: Callable[[Any], Any] | None = None 1acdebfg

1434 if lenient_issubclass(type_, Path): 1acdebfg

1435 convertor = param_path_convertor 1acdebfg

1436 if lenient_issubclass(type_, Enum): 1acdebfg

1437 convertor = generate_enum_convertor(type_) 1acdebfg

1438 return convertor 1acdebfg

1439 

1440 

1441def param_path_convertor(value: str | None = None) -> Path | None: 1acdebfg

1442 if value is not None: 1acdebfg

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

1444 # it back to a Path 

1445 return value if isinstance(value, Path) else Path(value) 1acdebfg

1446 return None 1acdebfg

1447 

1448 

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

1450 val_map = {str(val.value): val for val in enum} 1acdebfg

1451 

1452 def convertor(value: Any) -> Any: 1acdebfg

1453 if value is not None: 1acdebfg

1454 val = str(value) 1acdebfg

1455 if val in val_map: 1acdebfg

1456 key = val_map[val] 1acdebfg

1457 return enum(key) 1acdebfg

1458 

1459 return convertor 1acdebfg

1460 

1461 

1462def generate_list_convertor( 1acdebfg

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

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

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

1466 if (value is None) or (default_value is None and len(value) == 0): 1acdebfg

1467 return None 1acdebfg

1468 return [convertor(v) if convertor else v for v in value] 1acdebfg

1469 

1470 return internal_convertor 1acdebfg

1471 

1472 

1473def generate_tuple_convertor( 1acdebfg

1474 types: Sequence[Any], 

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

1476 convertors = [determine_type_convertor(type_) for type_ in types] 1acdebfg

1477 

1478 def internal_convertor( 1acdebfg

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

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

1481 if param_args is None: 1acdebfg

1482 return None 1acdebfg

1483 return tuple( 1acdebfg

1484 convertor(arg) if convertor else arg 

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

1486 ) 

1487 

1488 return internal_convertor 1acdebfg

1489 

1490 

1491def get_callback( 1acdebfg

1492 *, 

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

1494 params: Sequence[_click.Parameter] = [], 

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

1496 context_param_name: str | None = None, 

1497 pretty_exceptions_short: bool, 

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

1499 use_convertors = convertors or {} 1acdebfg

1500 if not callback: 1acdebfg

1501 return None 1acdebfg

1502 parameters = get_params_from_function(callback) 1acdebfg

1503 use_params: dict[str, Any] = {} 1acdebfg

1504 for param_name in parameters: 1acdebfg

1505 use_params[param_name] = None 1acdebfg

1506 for param in params: 1acdebfg

1507 if param.name: 1acdebfg

1508 use_params[param.name] = param.default 1acdebfg

1509 

1510 def wrapper(**kwargs: Any) -> Any: 1acdebfg

1511 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1acdebfg

1512 for k, v in kwargs.items(): 1acdebfg

1513 if k in use_convertors: 1acdebfg

1514 use_params[k] = use_convertors[k](v) 1acdebfg

1515 else: 

1516 use_params[k] = v 1acdebfg

1517 if context_param_name: 1acdebfg

1518 use_params[context_param_name] = get_current_context() 1acdebfg

1519 return callback(**use_params) 1acdebfg

1520 

1521 update_wrapper(wrapper, callback) 1acdebfg

1522 return wrapper 1acdebfg

1523 

1524 

1525def get_click_type( 1acdebfg

1526 *, annotation: Any, parameter_info: ParameterInfo 

1527) -> types.ParamType: 

1528 if parameter_info.click_type is not None: 1acdebfg

1529 return parameter_info.click_type 1acdebfg

1530 

1531 elif parameter_info.parser is not None: 1acdebfg

1532 return types.FuncParamType(parameter_info.parser) 1acdebfg

1533 

1534 elif annotation is str: 1acdebfg

1535 return types.STRING 1acdebfg

1536 elif annotation is int: 1acdebfg

1537 if parameter_info.min is not None or parameter_info.max is not None: 1acdebfg

1538 min_ = None 1acdebfg

1539 max_ = None 1acdebfg

1540 if parameter_info.min is not None: 1acdebfg

1541 min_ = int(parameter_info.min) 1acdebfg

1542 if parameter_info.max is not None: 1acdebfg

1543 max_ = int(parameter_info.max) 1acdebfg

1544 return types.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1acdebfg

1545 else: 

1546 return types.INT 1acdebfg

1547 elif annotation is float: 1acdebfg

1548 if parameter_info.min is not None or parameter_info.max is not None: 1acdebfg

1549 return types.FloatRange( 1acdebfg

1550 min=parameter_info.min, 

1551 max=parameter_info.max, 

1552 clamp=parameter_info.clamp, 

1553 ) 

1554 else: 

1555 return types.FLOAT 1acdebfg

1556 elif annotation is bool: 1acdebfg

1557 return types.BOOL 1acdebfg

1558 elif annotation == UUID: 1acdebfg

1559 return types.UUID 1acdebfg

1560 elif annotation == datetime: 1acdebfg

1561 return types.DateTime(formats=parameter_info.formats) 1acdebfg

1562 elif ( 

1563 annotation == Path 

1564 or parameter_info.allow_dash 

1565 or parameter_info.path_type 

1566 or parameter_info.resolve_path 

1567 ): 

1568 return TyperPath( 1acdebfg

1569 exists=parameter_info.exists, 

1570 file_okay=parameter_info.file_okay, 

1571 dir_okay=parameter_info.dir_okay, 

1572 writable=parameter_info.writable, 

1573 readable=parameter_info.readable, 

1574 resolve_path=parameter_info.resolve_path, 

1575 allow_dash=parameter_info.allow_dash, 

1576 path_type=parameter_info.path_type, 

1577 ) 

1578 elif lenient_issubclass(annotation, FileTextWrite): 1acdebfg

1579 return types.File( 1acdebfg

1580 mode=parameter_info.mode or "w", 

1581 encoding=parameter_info.encoding, 

1582 errors=parameter_info.errors, 

1583 lazy=parameter_info.lazy, 

1584 atomic=parameter_info.atomic, 

1585 ) 

1586 elif lenient_issubclass(annotation, FileText): 1acdebfg

1587 return types.File( 1acdebfg

1588 mode=parameter_info.mode or "r", 

1589 encoding=parameter_info.encoding, 

1590 errors=parameter_info.errors, 

1591 lazy=parameter_info.lazy, 

1592 atomic=parameter_info.atomic, 

1593 ) 

1594 elif lenient_issubclass(annotation, FileBinaryRead): 1acdebfg

1595 return types.File( 1acdebfg

1596 mode=parameter_info.mode or "rb", 

1597 encoding=parameter_info.encoding, 

1598 errors=parameter_info.errors, 

1599 lazy=parameter_info.lazy, 

1600 atomic=parameter_info.atomic, 

1601 ) 

1602 elif lenient_issubclass(annotation, FileBinaryWrite): 1acdebfg

1603 return types.File( 1acdebfg

1604 mode=parameter_info.mode or "wb", 

1605 encoding=parameter_info.encoding, 

1606 errors=parameter_info.errors, 

1607 lazy=parameter_info.lazy, 

1608 atomic=parameter_info.atomic, 

1609 ) 

1610 elif lenient_issubclass(annotation, Enum): 1acdebfg

1611 return TyperChoice( 1acdebfg

1612 [item.value for item in annotation], 

1613 case_sensitive=parameter_info.case_sensitive, 

1614 ) 

1615 elif is_literal_type(annotation): 1acdebfg

1616 return TyperChoice( 1acdebfg

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: 1acdebfg

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

1625 

1626 

1627def get_click_param( 1acdebfg

1628 param: ParamMeta, 

1629) -> tuple[TyperArgument | TyperOption, Any]: 

1630 # First, find out what will be: 

1631 # * ParamInfo (ArgumentInfo or OptionInfo) 

1632 # * default_value 

1633 # * required 

1634 default_value = None 1acdebfg

1635 required = False 1acdebfg

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

1637 parameter_info = param.default 1acdebfg

1638 if parameter_info.default == Required: 1acdebfg

1639 required = True 1acdebfg

1640 else: 

1641 default_value = parameter_info.default 1acdebfg

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

1643 required = True 1acdebfg

1644 parameter_info = ArgumentInfo() 1acdebfg

1645 else: 

1646 default_value = param.default 1acdebfg

1647 parameter_info = OptionInfo() 1acdebfg

1648 annotation: Any 

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

1650 annotation = param.annotation 1acdebfg

1651 else: 

1652 annotation = str 1acdebfg

1653 main_type = annotation 1acdebfg

1654 is_list = False 1acdebfg

1655 is_tuple = False 1acdebfg

1656 parameter_type: Any = None 1acdebfg

1657 is_flag = None 1acdebfg

1658 origin = get_origin(main_type) 1acdebfg

1659 

1660 if origin is not None: 1acdebfg

1661 # Handle SomeType | None and Optional[SomeType] 

1662 if is_union(origin): 1acdebfg

1663 types = [] 1acdebfg

1664 for type_ in get_args(main_type): 1acdebfg

1665 if type_ is NoneType: 1acdebfg

1666 continue 1acdebfg

1667 types.append(type_) 1acdebfg

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

1669 main_type = types[0] 1acdebfg

1670 origin = get_origin(main_type) 1acdebfg

1671 # Handle Tuples and Lists 

1672 if lenient_issubclass(origin, list): 1acdebfg

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

1674 assert not get_origin(main_type), ( 1acdebfg

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

1676 ) 

1677 is_list = True 1acdebfg

1678 elif lenient_issubclass(origin, tuple): 1acdebfg

1679 types = [] 1acdebfg

1680 for type_ in get_args(main_type): 1acdebfg

1681 assert not get_origin(type_), ( 1acdebfg

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

1683 ) 

1684 types.append( 1acdebfg

1685 get_click_type(annotation=type_, parameter_info=parameter_info) 

1686 ) 

1687 parameter_type = tuple(types) 1acdebfg

1688 is_tuple = True 1acdebfg

1689 if parameter_type is None: 1acdebfg

1690 parameter_type = get_click_type( 1acdebfg

1691 annotation=main_type, parameter_info=parameter_info 

1692 ) 

1693 convertor = determine_type_convertor(main_type) 1acdebfg

1694 if is_list: 1acdebfg

1695 convertor = generate_list_convertor( 1acdebfg

1696 convertor=convertor, default_value=default_value 

1697 ) 

1698 if is_tuple: 1acdebfg

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

1700 if isinstance(parameter_info, OptionInfo): 1acdebfg

1701 if main_type is bool: 1acdebfg

1702 is_flag = True 1acdebfg

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

1706 default_option_name = get_default_option_flag_name(param.name) 1acdebfg

1707 if is_flag: 1acdebfg

1708 default_option_declaration = ( 1acdebfg

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

1710 ) 

1711 else: 

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

1713 param_decls = [param.name] 1acdebfg

1714 if parameter_info.param_decls: 1acdebfg

1715 param_decls.extend(parameter_info.param_decls) 1acdebfg

1716 else: 

1717 param_decls.append(default_option_declaration) 1acdebfg

1718 return ( 1acdebfg

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): 1acdebfg

1754 param_decls = [param.name] 1acdebfg

1755 nargs = None 1acdebfg

1756 if is_list: 1acdebfg

1757 nargs = -1 1acdebfg

1758 return ( 1acdebfg

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( 1acdebfg

1791 *, 

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

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

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

1795 if not callback: 1acdebfg

1796 return None 1acdebfg

1797 parameters = get_params_from_function(callback) 1acdebfg

1798 ctx_name = None 1acdebfg

1799 click_param_name = None 1acdebfg

1800 value_name = None 1acdebfg

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

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

1803 if lenient_issubclass(param_sig.annotation, _click.Context): 1acdebfg

1804 ctx_name = param_name 1acdebfg

1805 elif lenient_issubclass(param_sig.annotation, _click.Parameter): 1acdebfg

1806 click_param_name = param_name 1acdebfg

1807 else: 

1808 untyped_names.append(param_name) 1acdebfg

1809 # Extract value param name first 

1810 if untyped_names: 1acdebfg

1811 value_name = untyped_names.pop() 1acdebfg

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

1813 if untyped_names: 1acdebfg

1814 if ctx_name is None: 1acdebfg

1815 ctx_name = untyped_names.pop(0) 1acdebfg

1816 if click_param_name is None: 1acdebfg

1817 if untyped_names: 1acdebfg

1818 click_param_name = untyped_names.pop(0) 1acdebfg

1819 if untyped_names: 1acdebfg

1820 raise _click.ClickException( 1acdebfg

1821 "Too many CLI parameter callback function parameters" 

1822 ) 

1823 

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

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

1826 if ctx_name: 1acdebfg

1827 use_params[ctx_name] = ctx 1acdebfg

1828 if click_param_name: 1acdebfg

1829 use_params[click_param_name] = param 1acdebfg

1830 if value_name: 1acdebfg

1831 if convertor: 1acdebfg

1832 use_value = convertor(value) 1acdebfg

1833 else: 

1834 use_value = value 1acdebfg

1835 use_params[value_name] = use_value 1acdebfg

1836 return callback(**use_params) 1acdebfg

1837 

1838 update_wrapper(wrapper, callback) 1acdebfg

1839 return wrapper 1acdebfg

1840 

1841 

1842def get_param_completion( 1acdebfg

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

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

1845 if not callback: 1acdebfg

1846 return None 1acdebfg

1847 parameters = get_params_from_function(callback) 1acdebfg

1848 ctx_name = None 1acdebfg

1849 args_name = None 1acdebfg

1850 incomplete_name = None 1acdebfg

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

1852 for param_sig in unassigned_params[:]: 1acdebfg

1853 origin = get_origin(param_sig.annotation) 1acdebfg

1854 if lenient_issubclass(param_sig.annotation, _click.Context): 1acdebfg

1855 ctx_name = param_sig.name 1acdebfg

1856 unassigned_params.remove(param_sig) 1acdebfg

1857 elif lenient_issubclass(origin, list): 1acdebfg

1858 args_name = param_sig.name 1acdebfg

1859 unassigned_params.remove(param_sig) 1acdebfg

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

1861 incomplete_name = param_sig.name 1acdebfg

1862 unassigned_params.remove(param_sig) 1acdebfg

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

1864 for param_sig in unassigned_params[:]: 1acdebfg

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

1866 ctx_name = param_sig.name 1acdebfg

1867 unassigned_params.remove(param_sig) 1acdebfg

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

1869 args_name = param_sig.name 1acdebfg

1870 unassigned_params.remove(param_sig) 1acdebfg

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

1872 incomplete_name = param_sig.name 1acdebfg

1873 unassigned_params.remove(param_sig) 1acdebfg

1874 # Extract value param name first 

1875 if unassigned_params: 1acdebfg

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

1877 raise _click.ClickException( 1acdebfg

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

1879 ) 

1880 

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

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

1883 if ctx_name: 1acdebfg

1884 use_params[ctx_name] = ctx 1acdebfg

1885 if args_name: 1acdebfg

1886 use_params[args_name] = args 1acdebfg

1887 if incomplete_name: 1acdebfg

1888 use_params[incomplete_name] = incomplete 1acdebfg

1889 return callback(**use_params) 1acdebfg

1890 

1891 update_wrapper(wrapper, callback) 1acdebfg

1892 return wrapper 1acdebfg

1893 

1894 

1895def run( 1acdebfg

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) 1acdebfg

1921 app.command()(function) 1acdebfg

1922 app() 1acdebfg

1923 

1924 

1925def _is_macos() -> bool: 1acdebfg

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

1927 

1928 

1929def _is_linux_or_bsd() -> bool: 1acdebfg

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

1931 return True 1acdebfg

1932 

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

1934 

1935 

1936def launch( 1acdebfg

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

1951 ), 

1952 ] = False, 

1953 locate: Annotated[ 

1954 bool, 

1955 Doc( 

1956 """ 

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

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

1959 """ 

1960 ), 

1961 ] = False, 

1962) -> int: 

1963 """ 

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

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

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

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

1968 success. 

1969 

1970 This function handles url in different operating systems separately: 

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

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

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

1974 

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

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

1977 

1978 ## Examples 

1979 ```python 

1980 import typer 

1981 

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

1983 ``` 

1984 

1985 ```python 

1986 import typer 

1987 

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

1989 ``` 

1990 """ 

1991 

1992 if url.startswith("http://") or url.startswith("https://"): 1acdebfg

1993 if _is_macos(): 1acdebfg

1994 return subprocess.Popen( 1acdebfg

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

1996 ).wait() 

1997 

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

1999 

2000 if has_xdg_open: 1acdebfg

2001 process = subprocess.Popen( 1acdebfg

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

2003 ) 

2004 if wait: 1acdebfg

2005 return process.wait() 1acdebfg

2006 return 0 1acdebfg

2007 

2008 import webbrowser 1acdebfg

2009 

2010 webbrowser.open(url) 1acdebfg

2011 

2012 return 0 1acdebfg

2013 

2014 else: 

2015 return _click.launch(url, wait=wait, locate=locate) 1acdebfg