Coverage for typer/main.py: 100%

496 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-12-19 20:29 +0000

1import inspect 1cefaghbdi

2import os 1cefaghbdi

3import platform 1cefaghbdi

4import shutil 1cefaghbdi

5import subprocess 1cefaghbdi

6import sys 1cefaghbdi

7import traceback 1cefaghbdi

8from datetime import datetime 1cefaghbdi

9from enum import Enum 1cefaghbdi

10from functools import update_wrapper 1cefaghbdi

11from pathlib import Path 1cefaghbdi

12from traceback import FrameSummary, StackSummary 1cefaghbdi

13from types import TracebackType 1cefaghbdi

14from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union 1cefaghbdi

15from uuid import UUID 1cefaghbdi

16 

17import click 1cefaghbdi

18from typer._types import TyperChoice 1cefaghbdi

19 

20from ._typing import get_args, get_origin, is_literal_type, is_union, literal_values 1cefaghbdi

21from .completion import get_completion_inspect_parameters 1cefaghbdi

22from .core import ( 1cefaghbdi

23 DEFAULT_MARKUP_MODE, 

24 HAS_RICH, 

25 MarkupMode, 

26 TyperArgument, 

27 TyperCommand, 

28 TyperGroup, 

29 TyperOption, 

30) 

31from .models import ( 1cefaghbdi

32 AnyType, 

33 ArgumentInfo, 

34 CommandFunctionType, 

35 CommandInfo, 

36 Default, 

37 DefaultPlaceholder, 

38 DeveloperExceptionConfig, 

39 FileBinaryRead, 

40 FileBinaryWrite, 

41 FileText, 

42 FileTextWrite, 

43 NoneType, 

44 OptionInfo, 

45 ParameterInfo, 

46 ParamMeta, 

47 Required, 

48 TyperInfo, 

49 TyperPath, 

50) 

51from .utils import get_params_from_function 1cefaghbdi

52 

53_original_except_hook = sys.excepthook 1cefaghbdi

54_typer_developer_exception_attr_name = "__typer_developer_exception__" 1cefaghbdi

55 

56 

57def except_hook( 1cefaghbdi

58 exc_type: Type[BaseException], exc_value: BaseException, tb: Optional[TracebackType] 

59) -> None: 

60 exception_config: Union[DeveloperExceptionConfig, None] = getattr( 1cefaghbdi

61 exc_value, _typer_developer_exception_attr_name, None 

62 ) 

63 standard_traceback = os.getenv( 1cefaghbdi

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

65 ) 

66 if ( 1cabd

67 standard_traceback 

68 or not exception_config 

69 or not exception_config.pretty_exceptions_enable 

70 ): 

71 _original_except_hook(exc_type, exc_value, tb) 1cefaghbdi

72 return 1cefaghbdi

73 typer_path = os.path.dirname(__file__) 1cefaghbdi

74 click_path = os.path.dirname(click.__file__) 1cefaghbdi

75 internal_dir_names = [typer_path, click_path] 1cefaghbdi

76 exc = exc_value 1cefaghbdi

77 if HAS_RICH: 1cefaghbdi

78 from . import rich_utils 1cefaghbdi

79 

80 rich_tb = rich_utils.get_traceback(exc, exception_config, internal_dir_names) 1cefaghbdi

81 console_stderr = rich_utils._get_rich_console(stderr=True) 1cefaghbdi

82 console_stderr.print(rich_tb) 1cefaghbdi

83 return 1cefaghbdi

84 tb_exc = traceback.TracebackException.from_exception(exc) 1cefaghbdi

85 stack: List[FrameSummary] = [] 1cefaghbdi

86 for frame in tb_exc.stack: 1cefaghbdi

87 if any(frame.filename.startswith(path) for path in internal_dir_names): 1cefaghbdi

88 if not exception_config.pretty_exceptions_short: 1cefaghbdi

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

90 stack.append( 1cefaghbdi

91 traceback.FrameSummary( 

92 filename=frame.filename, 

93 lineno=frame.lineno, 

94 name=frame.name, 

95 line="", 

96 ) 

97 ) 

98 else: 

99 stack.append(frame) 1cefaghbdi

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

101 final_stack_summary = StackSummary.from_list(stack) 1cefaghbdi

102 tb_exc.stack = final_stack_summary 1cefaghbdi

103 for line in tb_exc.format(): 1cefaghbdi

104 print(line, file=sys.stderr) 1cefaghbdi

105 return 1cefaghbdi

106 

107 

108def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]: 1cefaghbdi

109 install_param, show_param = get_completion_inspect_parameters() 1cefaghbdi

110 click_install_param, _ = get_click_param(install_param) 1cefaghbdi

111 click_show_param, _ = get_click_param(show_param) 1cefaghbdi

112 return click_install_param, click_show_param 1cefaghbdi

113 

114 

115class Typer: 1cefaghbdi

116 def __init__( 1cefaghbdi

117 self, 

118 *, 

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

120 cls: Optional[Type[TyperGroup]] = Default(None), 

121 invoke_without_command: bool = Default(False), 

122 no_args_is_help: bool = Default(False), 

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

124 chain: bool = Default(False), 

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

126 # Command 

127 context_settings: Optional[Dict[Any, Any]] = Default(None), 

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

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

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

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

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

133 add_help_option: bool = Default(True), 

134 hidden: bool = Default(False), 

135 deprecated: bool = Default(False), 

136 add_completion: bool = True, 

137 # Rich settings 

138 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

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

140 suggest_commands: bool = True, 

141 pretty_exceptions_enable: bool = True, 

142 pretty_exceptions_show_locals: bool = True, 

143 pretty_exceptions_short: bool = True, 

144 ): 

145 self._add_completion = add_completion 1cefaghbdi

146 self.rich_markup_mode: MarkupMode = rich_markup_mode 1cefaghbdi

147 self.rich_help_panel = rich_help_panel 1cefaghbdi

148 self.suggest_commands = suggest_commands 1cefaghbdi

149 self.pretty_exceptions_enable = pretty_exceptions_enable 1cefaghbdi

150 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1cefaghbdi

151 self.pretty_exceptions_short = pretty_exceptions_short 1cefaghbdi

152 self.info = TyperInfo( 1cefaghbdi

153 name=name, 

154 cls=cls, 

155 invoke_without_command=invoke_without_command, 

156 no_args_is_help=no_args_is_help, 

157 subcommand_metavar=subcommand_metavar, 

158 chain=chain, 

159 result_callback=result_callback, 

160 context_settings=context_settings, 

161 callback=callback, 

162 help=help, 

163 epilog=epilog, 

164 short_help=short_help, 

165 options_metavar=options_metavar, 

166 add_help_option=add_help_option, 

167 hidden=hidden, 

168 deprecated=deprecated, 

169 ) 

170 self.registered_groups: List[TyperInfo] = [] 1cefaghbdi

171 self.registered_commands: List[CommandInfo] = [] 1cefaghbdi

172 self.registered_callback: Optional[TyperInfo] = None 1cefaghbdi

173 

174 def callback( 1cefaghbdi

175 self, 

176 *, 

177 cls: Optional[Type[TyperGroup]] = Default(None), 

178 invoke_without_command: bool = Default(False), 

179 no_args_is_help: bool = Default(False), 

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

181 chain: bool = Default(False), 

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

183 # Command 

184 context_settings: Optional[Dict[Any, Any]] = Default(None), 

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

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

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

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

189 add_help_option: bool = Default(True), 

190 hidden: bool = Default(False), 

191 deprecated: bool = Default(False), 

192 # Rich settings 

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

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

195 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1cefaghbdi

196 self.registered_callback = TyperInfo( 1cefaghbdi

197 cls=cls, 

198 invoke_without_command=invoke_without_command, 

199 no_args_is_help=no_args_is_help, 

200 subcommand_metavar=subcommand_metavar, 

201 chain=chain, 

202 result_callback=result_callback, 

203 context_settings=context_settings, 

204 callback=f, 

205 help=help, 

206 epilog=epilog, 

207 short_help=short_help, 

208 options_metavar=( 

209 options_metavar or self._info_val_str("options_metavar") 

210 ), 

211 add_help_option=add_help_option, 

212 hidden=hidden, 

213 deprecated=deprecated, 

214 rich_help_panel=rich_help_panel, 

215 ) 

216 return f 1cefaghbdi

217 

218 return decorator 1cefaghbdi

219 

220 def command( 1cefaghbdi

221 self, 

222 name: Optional[str] = None, 

223 *, 

224 cls: Optional[Type[TyperCommand]] = None, 

225 context_settings: Optional[Dict[Any, Any]] = None, 

226 help: Optional[str] = None, 

227 epilog: Optional[str] = None, 

228 short_help: Optional[str] = None, 

229 options_metavar: Optional[str] = None, 

230 add_help_option: bool = True, 

231 no_args_is_help: bool = False, 

232 hidden: bool = False, 

233 deprecated: bool = False, 

234 # Rich settings 

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

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

237 if cls is None: 1cefaghbdi

238 cls = TyperCommand 1cefaghbdi

239 

240 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1cefaghbdi

241 self.registered_commands.append( 1cefaghbdi

242 CommandInfo( 

243 name=name, 

244 cls=cls, 

245 context_settings=context_settings, 

246 callback=f, 

247 help=help, 

248 epilog=epilog, 

249 short_help=short_help, 

250 options_metavar=( 

251 options_metavar or self._info_val_str("options_metavar") 

252 ), 

253 add_help_option=add_help_option, 

254 no_args_is_help=no_args_is_help, 

255 hidden=hidden, 

256 deprecated=deprecated, 

257 # Rich settings 

258 rich_help_panel=rich_help_panel, 

259 ) 

260 ) 

261 return f 1cefaghbdi

262 

263 return decorator 1cefaghbdi

264 

265 def add_typer( 1cefaghbdi

266 self, 

267 typer_instance: "Typer", 

268 *, 

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

270 cls: Optional[Type[TyperGroup]] = Default(None), 

271 invoke_without_command: bool = Default(False), 

272 no_args_is_help: bool = Default(False), 

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

274 chain: bool = Default(False), 

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

276 # Command 

277 context_settings: Optional[Dict[Any, Any]] = Default(None), 

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

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

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

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

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

283 add_help_option: bool = Default(True), 

284 hidden: bool = Default(False), 

285 deprecated: bool = Default(False), 

286 # Rich settings 

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

288 ) -> None: 

289 self.registered_groups.append( 1cefaghbdi

290 TyperInfo( 

291 typer_instance, 

292 name=name, 

293 cls=cls, 

294 invoke_without_command=invoke_without_command, 

295 no_args_is_help=no_args_is_help, 

296 subcommand_metavar=subcommand_metavar, 

297 chain=chain, 

298 result_callback=result_callback, 

299 context_settings=context_settings, 

300 callback=callback, 

301 help=help, 

302 epilog=epilog, 

303 short_help=short_help, 

304 options_metavar=( 

305 options_metavar or self._info_val_str("options_metavar") 

306 ), 

307 add_help_option=add_help_option, 

308 hidden=hidden, 

309 deprecated=deprecated, 

310 rich_help_panel=rich_help_panel, 

311 ) 

312 ) 

313 

314 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1cefaghbdi

315 if sys.excepthook != except_hook: 1cefaghbdi

316 sys.excepthook = except_hook 1cefaghbdi

317 try: 1cefaghbdi

318 return get_command(self)(*args, **kwargs) 1cefaghbdi

319 except Exception as e: 1cefaghbdi

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

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

322 # raise custom_exc from e 

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

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

325 # actual error last. 

326 setattr( 1cefaghbdi

327 e, 

328 _typer_developer_exception_attr_name, 

329 DeveloperExceptionConfig( 

330 pretty_exceptions_enable=self.pretty_exceptions_enable, 

331 pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, 

332 pretty_exceptions_short=self.pretty_exceptions_short, 

333 ), 

334 ) 

335 raise e 1cefaghbdi

336 

337 def _info_val_str(self, name: str) -> str: 1cefaghbdi

338 val = getattr(self.info, name) 1cefaghbdi

339 val_str = val.value if isinstance(val, DefaultPlaceholder) else val 1cefaghbdi

340 assert isinstance(val_str, str) 1cefaghbdi

341 return val_str 1cefaghbdi

342 

343 

344def get_group(typer_instance: Typer) -> TyperGroup: 1cefaghbdi

345 group = get_group_from_info( 1cefaghbdi

346 TyperInfo(typer_instance), 

347 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

348 rich_markup_mode=typer_instance.rich_markup_mode, 

349 suggest_commands=typer_instance.suggest_commands, 

350 ) 

351 return group 1cefaghbdi

352 

353 

354def get_command(typer_instance: Typer) -> click.Command: 1cefaghbdi

355 if typer_instance._add_completion: 1cefaghbdi

356 click_install_param, click_show_param = get_install_completion_arguments() 1cefaghbdi

357 if ( 1cabd

358 typer_instance.registered_callback 

359 or typer_instance.info.callback 

360 or typer_instance.registered_groups 

361 or len(typer_instance.registered_commands) > 1 

362 ): 

363 # Create a Group 

364 click_command: click.Command = get_group(typer_instance) 1cefaghbdi

365 if typer_instance._add_completion: 1cefaghbdi

366 click_command.params.append(click_install_param) 1cefaghbdi

367 click_command.params.append(click_show_param) 1cefaghbdi

368 return click_command 1cefaghbdi

369 elif len(typer_instance.registered_commands) == 1: 1cefaghbdi

370 # Create a single Command 

371 single_command = typer_instance.registered_commands[0] 1cefaghbdi

372 

373 if not single_command.context_settings and not isinstance( 1cefaghbdi

374 typer_instance.info.context_settings, DefaultPlaceholder 

375 ): 

376 single_command.context_settings = typer_instance.info.context_settings 1cefaghbdi

377 

378 click_command = get_command_from_info( 1cefaghbdi

379 single_command, 

380 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

381 rich_markup_mode=typer_instance.rich_markup_mode, 

382 ) 

383 if typer_instance._add_completion: 1cefaghbdi

384 click_command.params.append(click_install_param) 1cefaghbdi

385 click_command.params.append(click_show_param) 1cefaghbdi

386 return click_command 1cefaghbdi

387 raise RuntimeError( 

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

389 ) # pragma: no cover 

390 

391 

392def solve_typer_info_help(typer_info: TyperInfo) -> str: 1cefaghbdi

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

394 if not isinstance(typer_info.help, DefaultPlaceholder): 1cefaghbdi

395 return inspect.cleandoc(typer_info.help or "") 1cefaghbdi

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

397 try: 1cefaghbdi

398 callback_help = typer_info.typer_instance.registered_callback.help 1cefaghbdi

399 if not isinstance(callback_help, DefaultPlaceholder): 1cefaghbdi

400 return inspect.cleandoc(callback_help or "") 1cefaghbdi

401 except AttributeError: 1cefaghbdi

402 pass 1cefaghbdi

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

404 try: 1cefaghbdi

405 instance_help = typer_info.typer_instance.info.help 1cefaghbdi

406 if not isinstance(instance_help, DefaultPlaceholder): 1cefaghbdi

407 return inspect.cleandoc(instance_help or "") 1cefaghbdi

408 except AttributeError: 1cefaghbdi

409 pass 1cefaghbdi

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

411 if typer_info.callback: 1cefaghbdi

412 doc = inspect.getdoc(typer_info.callback) 1cefaghbdi

413 if doc: 1cefaghbdi

414 return doc 1cefaghbdi

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

416 try: 1cefaghbdi

417 callback = typer_info.typer_instance.registered_callback.callback 1cefaghbdi

418 if not isinstance(callback, DefaultPlaceholder): 1cefaghbdi

419 doc = inspect.getdoc(callback or "") 1cefaghbdi

420 if doc: 1cefaghbdi

421 return doc 1cefaghbdi

422 except AttributeError: 1cefaghbdi

423 pass 1cefaghbdi

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

425 try: 1cefaghbdi

426 instance_callback = typer_info.typer_instance.info.callback 1cefaghbdi

427 if not isinstance(instance_callback, DefaultPlaceholder): 1cefaghbdi

428 doc = inspect.getdoc(instance_callback) 1cefaghbdi

429 if doc: 1cefaghbdi

430 return doc 1cefaghbdi

431 except AttributeError: 1cefaghbdi

432 pass 1cefaghbdi

433 # Value not set, use the default 

434 return typer_info.help.value 1cefaghbdi

435 

436 

437def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1cefaghbdi

438 values: Dict[str, Any] = {} 1cefaghbdi

439 for name, value in typer_info.__dict__.items(): 1cefaghbdi

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

441 if not isinstance(value, DefaultPlaceholder): 1cefaghbdi

442 values[name] = value 1cefaghbdi

443 continue 1cefaghbdi

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

445 try: 1cefaghbdi

446 callback_value = getattr( 1cefaghbdi

447 typer_info.typer_instance.registered_callback, # type: ignore 

448 name, 

449 ) 

450 if not isinstance(callback_value, DefaultPlaceholder): 1cefaghbdi

451 values[name] = callback_value 1cefaghbdi

452 continue 1cefaghbdi

453 except AttributeError: 1cefaghbdi

454 pass 1cefaghbdi

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

456 try: 1cefaghbdi

457 instance_value = getattr( 1cefaghbdi

458 typer_info.typer_instance.info, # type: ignore 

459 name, 

460 ) 

461 if not isinstance(instance_value, DefaultPlaceholder): 1cefaghbdi

462 values[name] = instance_value 1cefaghbdi

463 continue 1cefaghbdi

464 except AttributeError: 1cefaghbdi

465 pass 1cefaghbdi

466 # Value not set, use the default 

467 values[name] = value.value 1cefaghbdi

468 values["help"] = solve_typer_info_help(typer_info) 1cefaghbdi

469 return TyperInfo(**values) 1cefaghbdi

470 

471 

472def get_group_from_info( 1cefaghbdi

473 group_info: TyperInfo, 

474 *, 

475 pretty_exceptions_short: bool, 

476 suggest_commands: bool, 

477 rich_markup_mode: MarkupMode, 

478) -> TyperGroup: 

479 assert group_info.typer_instance, ( 1cefaghbdi

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

481 ) 

482 commands: Dict[str, click.Command] = {} 1cefaghbdi

483 for command_info in group_info.typer_instance.registered_commands: 1cefaghbdi

484 command = get_command_from_info( 1cefaghbdi

485 command_info=command_info, 

486 pretty_exceptions_short=pretty_exceptions_short, 

487 rich_markup_mode=rich_markup_mode, 

488 ) 

489 if command.name: 1cefaghbdi

490 commands[command.name] = command 1cefaghbdi

491 for sub_group_info in group_info.typer_instance.registered_groups: 1cefaghbdi

492 sub_group = get_group_from_info( 1cefaghbdi

493 sub_group_info, 

494 pretty_exceptions_short=pretty_exceptions_short, 

495 rich_markup_mode=rich_markup_mode, 

496 suggest_commands=suggest_commands, 

497 ) 

498 if sub_group.name: 1cefaghbdi

499 commands[sub_group.name] = sub_group 1cefaghbdi

500 else: 

501 if sub_group.callback: 1cefaghbdi

502 import warnings 1cefaghbdi

503 

504 warnings.warn( 1cefaghbdi

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

506 stacklevel=5, 

507 ) 

508 for sub_command_name, sub_command in sub_group.commands.items(): 1cefaghbdi

509 commands[sub_command_name] = sub_command 1cefaghbdi

510 solved_info = solve_typer_info_defaults(group_info) 1cefaghbdi

511 ( 1cefaghbdi

512 params, 

513 convertors, 

514 context_param_name, 

515 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) 

516 cls = solved_info.cls or TyperGroup 1cefaghbdi

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

518 group = cls( 1cefaghbdi

519 name=solved_info.name or "", 

520 commands=commands, 

521 invoke_without_command=solved_info.invoke_without_command, 

522 no_args_is_help=solved_info.no_args_is_help, 

523 subcommand_metavar=solved_info.subcommand_metavar, 

524 chain=solved_info.chain, 

525 result_callback=solved_info.result_callback, 

526 context_settings=solved_info.context_settings, 

527 callback=get_callback( 

528 callback=solved_info.callback, 

529 params=params, 

530 convertors=convertors, 

531 context_param_name=context_param_name, 

532 pretty_exceptions_short=pretty_exceptions_short, 

533 ), 

534 params=params, 

535 help=solved_info.help, 

536 epilog=solved_info.epilog, 

537 short_help=solved_info.short_help, 

538 options_metavar=solved_info.options_metavar, 

539 add_help_option=solved_info.add_help_option, 

540 hidden=solved_info.hidden, 

541 deprecated=solved_info.deprecated, 

542 rich_markup_mode=rich_markup_mode, 

543 # Rich settings 

544 rich_help_panel=solved_info.rich_help_panel, 

545 suggest_commands=suggest_commands, 

546 ) 

547 return group 1cefaghbdi

548 

549 

550def get_command_name(name: str) -> str: 1cefaghbdi

551 return name.lower().replace("_", "-") 1cefaghbdi

552 

553 

554def get_params_convertors_ctx_param_name_from_function( 1cefaghbdi

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

556) -> Tuple[List[Union[click.Argument, click.Option]], Dict[str, Any], Optional[str]]: 

557 params = [] 1cefaghbdi

558 convertors = {} 1cefaghbdi

559 context_param_name = None 1cefaghbdi

560 if callback: 1cefaghbdi

561 parameters = get_params_from_function(callback) 1cefaghbdi

562 for param_name, param in parameters.items(): 1cefaghbdi

563 if lenient_issubclass(param.annotation, click.Context): 1cefaghbdi

564 context_param_name = param_name 1cefaghbdi

565 continue 1cefaghbdi

566 click_param, convertor = get_click_param(param) 1cefaghbdi

567 if convertor: 1cefaghbdi

568 convertors[param_name] = convertor 1cefaghbdi

569 params.append(click_param) 1cefaghbdi

570 return params, convertors, context_param_name 1cefaghbdi

571 

572 

573def get_command_from_info( 1cefaghbdi

574 command_info: CommandInfo, 

575 *, 

576 pretty_exceptions_short: bool, 

577 rich_markup_mode: MarkupMode, 

578) -> click.Command: 

579 assert command_info.callback, "A command must have a callback function" 1cefaghbdi

580 name = command_info.name or get_command_name(command_info.callback.__name__) 1cefaghbdi

581 use_help = command_info.help 1cefaghbdi

582 if use_help is None: 1cefaghbdi

583 use_help = inspect.getdoc(command_info.callback) 1cefaghbdi

584 else: 

585 use_help = inspect.cleandoc(use_help) 1cefaghbdi

586 ( 1cefaghbdi

587 params, 

588 convertors, 

589 context_param_name, 

590 ) = get_params_convertors_ctx_param_name_from_function(command_info.callback) 

591 cls = command_info.cls or TyperCommand 1cefaghbdi

592 command = cls( 1cefaghbdi

593 name=name, 

594 context_settings=command_info.context_settings, 

595 callback=get_callback( 

596 callback=command_info.callback, 

597 params=params, 

598 convertors=convertors, 

599 context_param_name=context_param_name, 

600 pretty_exceptions_short=pretty_exceptions_short, 

601 ), 

602 params=params, # type: ignore 

603 help=use_help, 

604 epilog=command_info.epilog, 

605 short_help=command_info.short_help, 

606 options_metavar=command_info.options_metavar, 

607 add_help_option=command_info.add_help_option, 

608 no_args_is_help=command_info.no_args_is_help, 

609 hidden=command_info.hidden, 

610 deprecated=command_info.deprecated, 

611 rich_markup_mode=rich_markup_mode, 

612 # Rich settings 

613 rich_help_panel=command_info.rich_help_panel, 

614 ) 

615 return command 1cefaghbdi

616 

617 

618def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]: 1cefaghbdi

619 convertor: Optional[Callable[[Any], Any]] = None 1cefaghbdi

620 if lenient_issubclass(type_, Path): 1cefaghbdi

621 convertor = param_path_convertor 1cefaghbdi

622 if lenient_issubclass(type_, Enum): 1cefaghbdi

623 convertor = generate_enum_convertor(type_) 1cefaghbdi

624 return convertor 1cefaghbdi

625 

626 

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

628 if value is not None: 1cefaghbdi

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

630 # it back to a Path 

631 return value if isinstance(value, Path) else Path(value) 1cefaghbdi

632 return None 1cefaghbdi

633 

634 

635def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]: 1cefaghbdi

636 val_map = {str(val.value): val for val in enum} 1cefaghbdi

637 

638 def convertor(value: Any) -> Any: 1cefaghbdi

639 if value is not None: 1cefaghbdi

640 val = str(value) 1cefaghbdi

641 if val in val_map: 1cefaghbdi

642 key = val_map[val] 1cefaghbdi

643 return enum(key) 1cefaghbdi

644 

645 return convertor 1cefaghbdi

646 

647 

648def generate_list_convertor( 1cefaghbdi

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

650) -> Callable[[Optional[Sequence[Any]]], Optional[List[Any]]]: 

651 def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[List[Any]]: 1cefaghbdi

652 if (value is None) or (default_value is None and len(value) == 0): 1cefaghbdi

653 return None 1cefaghbdi

654 return [convertor(v) if convertor else v for v in value] 1cefaghbdi

655 

656 return internal_convertor 1cefaghbdi

657 

658 

659def generate_tuple_convertor( 1cefaghbdi

660 types: Sequence[Any], 

661) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]: 

662 convertors = [determine_type_convertor(type_) for type_ in types] 1cefaghbdi

663 

664 def internal_convertor( 1cefaghbdi

665 param_args: Optional[Tuple[Any, ...]], 

666 ) -> Optional[Tuple[Any, ...]]: 

667 if param_args is None: 1cefaghbdi

668 return None 1cefaghbdi

669 return tuple( 1cefaghbdi

670 convertor(arg) if convertor else arg 

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

672 ) 

673 

674 return internal_convertor 1cefaghbdi

675 

676 

677def get_callback( 1cefaghbdi

678 *, 

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

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

681 convertors: Optional[Dict[str, Callable[[str], Any]]] = None, 

682 context_param_name: Optional[str] = None, 

683 pretty_exceptions_short: bool, 

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

685 use_convertors = convertors or {} 1cefaghbdi

686 if not callback: 1cefaghbdi

687 return None 1cefaghbdi

688 parameters = get_params_from_function(callback) 1cefaghbdi

689 use_params: Dict[str, Any] = {} 1cefaghbdi

690 for param_name in parameters: 1cefaghbdi

691 use_params[param_name] = None 1cefaghbdi

692 for param in params: 1cefaghbdi

693 if param.name: 1cefaghbdi

694 use_params[param.name] = param.default 1cefaghbdi

695 

696 def wrapper(**kwargs: Any) -> Any: 1cefaghbdi

697 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1cefaghbdi

698 for k, v in kwargs.items(): 1cefaghbdi

699 if k in use_convertors: 1cefaghbdi

700 use_params[k] = use_convertors[k](v) 1cefaghbdi

701 else: 

702 use_params[k] = v 1cefaghbdi

703 if context_param_name: 1cefaghbdi

704 use_params[context_param_name] = click.get_current_context() 1cefaghbdi

705 return callback(**use_params) 1cefaghbdi

706 

707 update_wrapper(wrapper, callback) 1cefaghbdi

708 return wrapper 1cefaghbdi

709 

710 

711def get_click_type( 1cefaghbdi

712 *, annotation: Any, parameter_info: ParameterInfo 

713) -> click.ParamType: 

714 if parameter_info.click_type is not None: 1cefaghbdi

715 return parameter_info.click_type 1cefaghbdi

716 

717 elif parameter_info.parser is not None: 1cefaghbdi

718 return click.types.FuncParamType(parameter_info.parser) 1cefaghbdi

719 

720 elif annotation is str: 1cefaghbdi

721 return click.STRING 1cefaghbdi

722 elif annotation is int: 1cefaghbdi

723 if parameter_info.min is not None or parameter_info.max is not None: 1cefaghbdi

724 min_ = None 1cefaghbdi

725 max_ = None 1cefaghbdi

726 if parameter_info.min is not None: 1cefaghbdi

727 min_ = int(parameter_info.min) 1cefaghbdi

728 if parameter_info.max is not None: 1cefaghbdi

729 max_ = int(parameter_info.max) 1cefaghbdi

730 return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1cefaghbdi

731 else: 

732 return click.INT 1cefaghbdi

733 elif annotation is float: 1cefaghbdi

734 if parameter_info.min is not None or parameter_info.max is not None: 1cefaghbdi

735 return click.FloatRange( 1cefaghbdi

736 min=parameter_info.min, 

737 max=parameter_info.max, 

738 clamp=parameter_info.clamp, 

739 ) 

740 else: 

741 return click.FLOAT 1cefaghbdi

742 elif annotation is bool: 1cefaghbdi

743 return click.BOOL 1cefaghbdi

744 elif annotation == UUID: 1cefaghbdi

745 return click.UUID 1cefaghbdi

746 elif annotation == datetime: 1cefaghbdi

747 return click.DateTime(formats=parameter_info.formats) 1cefaghbdi

748 elif ( 1ab

749 annotation == Path 

750 or parameter_info.allow_dash 

751 or parameter_info.path_type 

752 or parameter_info.resolve_path 

753 ): 

754 return TyperPath( 1cefaghbdi

755 exists=parameter_info.exists, 

756 file_okay=parameter_info.file_okay, 

757 dir_okay=parameter_info.dir_okay, 

758 writable=parameter_info.writable, 

759 readable=parameter_info.readable, 

760 resolve_path=parameter_info.resolve_path, 

761 allow_dash=parameter_info.allow_dash, 

762 path_type=parameter_info.path_type, 

763 ) 

764 elif lenient_issubclass(annotation, FileTextWrite): 1cefaghbdi

765 return click.File( 1cefaghbdi

766 mode=parameter_info.mode or "w", 

767 encoding=parameter_info.encoding, 

768 errors=parameter_info.errors, 

769 lazy=parameter_info.lazy, 

770 atomic=parameter_info.atomic, 

771 ) 

772 elif lenient_issubclass(annotation, FileText): 1cefaghbdi

773 return click.File( 1cefaghbdi

774 mode=parameter_info.mode or "r", 

775 encoding=parameter_info.encoding, 

776 errors=parameter_info.errors, 

777 lazy=parameter_info.lazy, 

778 atomic=parameter_info.atomic, 

779 ) 

780 elif lenient_issubclass(annotation, FileBinaryRead): 1cefaghbdi

781 return click.File( 1cefaghbdi

782 mode=parameter_info.mode or "rb", 

783 encoding=parameter_info.encoding, 

784 errors=parameter_info.errors, 

785 lazy=parameter_info.lazy, 

786 atomic=parameter_info.atomic, 

787 ) 

788 elif lenient_issubclass(annotation, FileBinaryWrite): 1cefaghbdi

789 return click.File( 1cefaghbdi

790 mode=parameter_info.mode or "wb", 

791 encoding=parameter_info.encoding, 

792 errors=parameter_info.errors, 

793 lazy=parameter_info.lazy, 

794 atomic=parameter_info.atomic, 

795 ) 

796 elif lenient_issubclass(annotation, Enum): 1cefaghbdi

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

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

799 # support for enum values but reading enum names. 

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

801 # Click < 8.2.0. 

802 return TyperChoice( 1cefaghbdi

803 [item.value for item in annotation], 

804 case_sensitive=parameter_info.case_sensitive, 

805 ) 

806 elif is_literal_type(annotation): 1cefaghbdi

807 return click.Choice( 1cefaghbdi

808 literal_values(annotation), 

809 case_sensitive=parameter_info.case_sensitive, 

810 ) 

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

812 

813 

814def lenient_issubclass( 1cefaghbdi

815 cls: Any, class_or_tuple: Union[AnyType, Tuple[AnyType, ...]] 

816) -> bool: 

817 return isinstance(cls, type) and issubclass(cls, class_or_tuple) 1cefaghbdi

818 

819 

820def get_click_param( 1cefaghbdi

821 param: ParamMeta, 

822) -> Tuple[Union[click.Argument, click.Option], Any]: 

823 # First, find out what will be: 

824 # * ParamInfo (ArgumentInfo or OptionInfo) 

825 # * default_value 

826 # * required 

827 default_value = None 1cefaghbdi

828 required = False 1cefaghbdi

829 if isinstance(param.default, ParameterInfo): 1cefaghbdi

830 parameter_info = param.default 1cefaghbdi

831 if parameter_info.default == Required: 1cefaghbdi

832 required = True 1cefaghbdi

833 else: 

834 default_value = parameter_info.default 1cefaghbdi

835 elif param.default == Required or param.default is param.empty: 1cefaghbdi

836 required = True 1cefaghbdi

837 parameter_info = ArgumentInfo() 1cefaghbdi

838 else: 

839 default_value = param.default 1cefaghbdi

840 parameter_info = OptionInfo() 1cefaghbdi

841 annotation: Any 

842 if param.annotation is not param.empty: 1cefaghbdi

843 annotation = param.annotation 1cefaghbdi

844 else: 

845 annotation = str 1cefaghbdi

846 main_type = annotation 1cefaghbdi

847 is_list = False 1cefaghbdi

848 is_tuple = False 1cefaghbdi

849 parameter_type: Any = None 1cefaghbdi

850 is_flag = None 1cefaghbdi

851 origin = get_origin(main_type) 1cefaghbdi

852 

853 if origin is not None: 1cefaghbdi

854 # Handle SomeType | None and Optional[SomeType] 

855 if is_union(origin): 1cefaghbdi

856 types = [] 1cefaghbdi

857 for type_ in get_args(main_type): 1cefaghbdi

858 if type_ is NoneType: 1cefaghbdi

859 continue 1cefaghbdi

860 types.append(type_) 1cefaghbdi

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

862 main_type = types[0] 1cefaghbdi

863 origin = get_origin(main_type) 1cefaghbdi

864 # Handle Tuples and Lists 

865 if lenient_issubclass(origin, List): 1cefaghbdi

866 main_type = get_args(main_type)[0] 1cefaghbdi

867 assert not get_origin(main_type), ( 1cefaghbdi

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

869 ) 

870 is_list = True 1cefaghbdi

871 elif lenient_issubclass(origin, Tuple): # type: ignore 1cefaghbdi

872 types = [] 1cefaghbdi

873 for type_ in get_args(main_type): 1cefaghbdi

874 assert not get_origin(type_), ( 1cefaghbdi

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

876 ) 

877 types.append( 1cefaghbdi

878 get_click_type(annotation=type_, parameter_info=parameter_info) 

879 ) 

880 parameter_type = tuple(types) 1cefaghbdi

881 is_tuple = True 1cefaghbdi

882 if parameter_type is None: 1cefaghbdi

883 parameter_type = get_click_type( 1cefaghbdi

884 annotation=main_type, parameter_info=parameter_info 

885 ) 

886 convertor = determine_type_convertor(main_type) 1cefaghbdi

887 if is_list: 1cefaghbdi

888 convertor = generate_list_convertor( 1cefaghbdi

889 convertor=convertor, default_value=default_value 

890 ) 

891 if is_tuple: 1cefaghbdi

892 convertor = generate_tuple_convertor(get_args(main_type)) 1cefaghbdi

893 if isinstance(parameter_info, OptionInfo): 1cefaghbdi

894 if main_type is bool: 1cefaghbdi

895 is_flag = True 1cefaghbdi

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

897 # to bool internally 

898 parameter_type = None 1cefaghbdi

899 default_option_name = get_command_name(param.name) 1cefaghbdi

900 if is_flag: 1cefaghbdi

901 default_option_declaration = ( 1cefaghbdi

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

903 ) 

904 else: 

905 default_option_declaration = f"--{default_option_name}" 1cefaghbdi

906 param_decls = [param.name] 1cefaghbdi

907 if parameter_info.param_decls: 1cefaghbdi

908 param_decls.extend(parameter_info.param_decls) 1cefaghbdi

909 else: 

910 param_decls.append(default_option_declaration) 1cefaghbdi

911 return ( 1cefaghbdi

912 TyperOption( 

913 # Option 

914 param_decls=param_decls, 

915 show_default=parameter_info.show_default, 

916 prompt=parameter_info.prompt, 

917 confirmation_prompt=parameter_info.confirmation_prompt, 

918 prompt_required=parameter_info.prompt_required, 

919 hide_input=parameter_info.hide_input, 

920 is_flag=is_flag, 

921 multiple=is_list, 

922 count=parameter_info.count, 

923 allow_from_autoenv=parameter_info.allow_from_autoenv, 

924 type=parameter_type, 

925 help=parameter_info.help, 

926 hidden=parameter_info.hidden, 

927 show_choices=parameter_info.show_choices, 

928 show_envvar=parameter_info.show_envvar, 

929 # Parameter 

930 required=required, 

931 default=default_value, 

932 callback=get_param_callback( 

933 callback=parameter_info.callback, convertor=convertor 

934 ), 

935 metavar=parameter_info.metavar, 

936 expose_value=parameter_info.expose_value, 

937 is_eager=parameter_info.is_eager, 

938 envvar=parameter_info.envvar, 

939 shell_complete=parameter_info.shell_complete, 

940 autocompletion=get_param_completion(parameter_info.autocompletion), 

941 # Rich settings 

942 rich_help_panel=parameter_info.rich_help_panel, 

943 ), 

944 convertor, 

945 ) 

946 elif isinstance(parameter_info, ArgumentInfo): 1cefaghbdi

947 param_decls = [param.name] 1cefaghbdi

948 nargs = None 1cefaghbdi

949 if is_list: 1cefaghbdi

950 nargs = -1 1cefaghbdi

951 return ( 1cefaghbdi

952 TyperArgument( 

953 # Argument 

954 param_decls=param_decls, 

955 type=parameter_type, 

956 required=required, 

957 nargs=nargs, 

958 # TyperArgument 

959 show_default=parameter_info.show_default, 

960 show_choices=parameter_info.show_choices, 

961 show_envvar=parameter_info.show_envvar, 

962 help=parameter_info.help, 

963 hidden=parameter_info.hidden, 

964 # Parameter 

965 default=default_value, 

966 callback=get_param_callback( 

967 callback=parameter_info.callback, convertor=convertor 

968 ), 

969 metavar=parameter_info.metavar, 

970 expose_value=parameter_info.expose_value, 

971 is_eager=parameter_info.is_eager, 

972 envvar=parameter_info.envvar, 

973 shell_complete=parameter_info.shell_complete, 

974 autocompletion=get_param_completion(parameter_info.autocompletion), 

975 # Rich settings 

976 rich_help_panel=parameter_info.rich_help_panel, 

977 ), 

978 convertor, 

979 ) 

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

981 

982 

983def get_param_callback( 1cefaghbdi

984 *, 

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

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

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

988 if not callback: 1cefaghbdi

989 return None 1cefaghbdi

990 parameters = get_params_from_function(callback) 1cefaghbdi

991 ctx_name = None 1cefaghbdi

992 click_param_name = None 1cefaghbdi

993 value_name = None 1cefaghbdi

994 untyped_names: List[str] = [] 1cefaghbdi

995 for param_name, param_sig in parameters.items(): 1cefaghbdi

996 if lenient_issubclass(param_sig.annotation, click.Context): 1cefaghbdi

997 ctx_name = param_name 1cefaghbdi

998 elif lenient_issubclass(param_sig.annotation, click.Parameter): 1cefaghbdi

999 click_param_name = param_name 1cefaghbdi

1000 else: 

1001 untyped_names.append(param_name) 1cefaghbdi

1002 # Extract value param name first 

1003 if untyped_names: 1cefaghbdi

1004 value_name = untyped_names.pop() 1cefaghbdi

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

1006 if untyped_names: 1cefaghbdi

1007 if ctx_name is None: 1cefaghbdi

1008 ctx_name = untyped_names.pop(0) 1cefaghbdi

1009 if click_param_name is None: 1cefaghbdi

1010 if untyped_names: 1cefaghbdi

1011 click_param_name = untyped_names.pop(0) 1cefaghbdi

1012 if untyped_names: 1cefaghbdi

1013 raise click.ClickException( 1cefaghbdi

1014 "Too many CLI parameter callback function parameters" 

1015 ) 

1016 

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

1018 use_params: Dict[str, Any] = {} 1cefaghbdi

1019 if ctx_name: 1cefaghbdi

1020 use_params[ctx_name] = ctx 1cefaghbdi

1021 if click_param_name: 1cefaghbdi

1022 use_params[click_param_name] = param 1cefaghbdi

1023 if value_name: 1cefaghbdi

1024 if convertor: 1cefaghbdi

1025 use_value = convertor(value) 1cefaghbdi

1026 else: 

1027 use_value = value 1cefaghbdi

1028 use_params[value_name] = use_value 1cefaghbdi

1029 return callback(**use_params) 1cefaghbdi

1030 

1031 update_wrapper(wrapper, callback) 1cefaghbdi

1032 return wrapper 1cefaghbdi

1033 

1034 

1035def get_param_completion( 1cefaghbdi

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

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

1038 if not callback: 1cefaghbdi

1039 return None 1cefaghbdi

1040 parameters = get_params_from_function(callback) 1cefaghbdi

1041 ctx_name = None 1cefaghbdi

1042 args_name = None 1cefaghbdi

1043 incomplete_name = None 1cefaghbdi

1044 unassigned_params = list(parameters.values()) 1cefaghbdi

1045 for param_sig in unassigned_params[:]: 1cefaghbdi

1046 origin = get_origin(param_sig.annotation) 1cefaghbdi

1047 if lenient_issubclass(param_sig.annotation, click.Context): 1cefaghbdi

1048 ctx_name = param_sig.name 1cefaghbdi

1049 unassigned_params.remove(param_sig) 1cefaghbdi

1050 elif lenient_issubclass(origin, List): 1cefaghbdi

1051 args_name = param_sig.name 1cefaghbdi

1052 unassigned_params.remove(param_sig) 1cefaghbdi

1053 elif lenient_issubclass(param_sig.annotation, str): 1cefaghbdi

1054 incomplete_name = param_sig.name 1cefaghbdi

1055 unassigned_params.remove(param_sig) 1cefaghbdi

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

1057 for param_sig in unassigned_params[:]: 1cefaghbdi

1058 if ctx_name is None and param_sig.name == "ctx": 1cefaghbdi

1059 ctx_name = param_sig.name 1cefaghbdi

1060 unassigned_params.remove(param_sig) 1cefaghbdi

1061 elif args_name is None and param_sig.name == "args": 1cefaghbdi

1062 args_name = param_sig.name 1cefaghbdi

1063 unassigned_params.remove(param_sig) 1cefaghbdi

1064 elif incomplete_name is None and param_sig.name == "incomplete": 1cefaghbdi

1065 incomplete_name = param_sig.name 1cefaghbdi

1066 unassigned_params.remove(param_sig) 1cefaghbdi

1067 # Extract value param name first 

1068 if unassigned_params: 1cefaghbdi

1069 show_params = " ".join([param.name for param in unassigned_params]) 1cefaghbdi

1070 raise click.ClickException( 1cefaghbdi

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

1072 ) 

1073 

1074 def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any: 1cefaghbdi

1075 use_params: Dict[str, Any] = {} 1cefaghbdi

1076 if ctx_name: 1cefaghbdi

1077 use_params[ctx_name] = ctx 1cefaghbdi

1078 if args_name: 1cefaghbdi

1079 use_params[args_name] = args 1cefaghbdi

1080 if incomplete_name: 1cefaghbdi

1081 use_params[incomplete_name] = incomplete 1cefaghbdi

1082 return callback(**use_params) 1cefaghbdi

1083 

1084 update_wrapper(wrapper, callback) 1cefaghbdi

1085 return wrapper 1cefaghbdi

1086 

1087 

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

1089 app = Typer(add_completion=False) 1cefaghbdi

1090 app.command()(function) 1cefaghbdi

1091 app() 1cefaghbdi

1092 

1093 

1094def _is_macos() -> bool: 1cefaghbdi

1095 return platform.system() == "Darwin" 1cefaghbdi

1096 

1097 

1098def _is_linux_or_bsd() -> bool: 1cefaghbdi

1099 if platform.system() == "Linux": 1cefaghbdi

1100 return True 1cefaghbdi

1101 

1102 return "BSD" in platform.system() 1cefaghbdi

1103 

1104 

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

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

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

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

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

1110 success. 

1111 

1112 This function handles url in different operating systems separately: 

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

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

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

1116 

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

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

1119 

1120 Examples:: 

1121 

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

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

1124 

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

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

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

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

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

1130 application associated with the URL it will attempt to 

1131 launch a file manager with the file located. This 

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

1133 the filesystem. 

1134 """ 

1135 

1136 if url.startswith("http://") or url.startswith("https://"): 1cefaghbdi

1137 if _is_macos(): 1cefaghbdi

1138 return subprocess.Popen( 1cefaghbdi

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

1140 ).wait() 

1141 

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

1143 

1144 if has_xdg_open: 1cefaghbdi

1145 return subprocess.Popen( 1cefaghbdi

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

1147 ).wait() 

1148 

1149 import webbrowser 1cefaghbdi

1150 

1151 webbrowser.open(url) 1cefaghbdi

1152 

1153 return 0 1cefaghbdi

1154 

1155 else: 

1156 return click.launch(url) 1cefaghbdi