Coverage for typer/main.py: 100%

480 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-09 18:26 +0000

1import inspect 1hceafbdg

2import os 1hceafbdg

3import sys 1hceafbdg

4import traceback 1hceafbdg

5from datetime import datetime 1hceafbdg

6from enum import Enum 1hceafbdg

7from functools import update_wrapper 1hceafbdg

8from pathlib import Path 1hceafbdg

9from traceback import FrameSummary, StackSummary 1hceafbdg

10from types import TracebackType 1hceafbdg

11from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union 1hceafbdg

12from uuid import UUID 1hceafbdg

13 

14import click 1hceafbdg

15from typing_extensions import get_args, get_origin 1hceafbdg

16 

17from ._typing import is_union 1hceafbdg

18from .completion import get_completion_inspect_parameters 1hceafbdg

19from .core import ( 1hceafbdg

20 DEFAULT_MARKUP_MODE, 

21 MarkupMode, 

22 TyperArgument, 

23 TyperCommand, 

24 TyperGroup, 

25 TyperOption, 

26) 

27from .models import ( 1hceafbdg

28 AnyType, 

29 ArgumentInfo, 

30 CommandFunctionType, 

31 CommandInfo, 

32 Default, 

33 DefaultPlaceholder, 

34 DeveloperExceptionConfig, 

35 FileBinaryRead, 

36 FileBinaryWrite, 

37 FileText, 

38 FileTextWrite, 

39 NoneType, 

40 OptionInfo, 

41 ParameterInfo, 

42 ParamMeta, 

43 Required, 

44 TyperInfo, 

45) 

46from .utils import get_params_from_function 1hceafbdg

47 

48try: 1hceafbdg

49 import rich 1hceafbdg

50 from rich.traceback import Traceback 1hceafbdg

51 

52 from . import rich_utils 1hceafbdg

53 

54 console_stderr = rich_utils._get_rich_console(stderr=True) 1hceafbdg

55 

56except ImportError: # pragma: no cover 

57 rich = None # type: ignore 

58 

59_original_except_hook = sys.excepthook 1hceafbdg

60_typer_developer_exception_attr_name = "__typer_developer_exception__" 1hceafbdg

61 

62 

63def except_hook( 1ceafbdg

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

65) -> None: 

66 exception_config: Union[DeveloperExceptionConfig, None] = getattr( 1hceafbdg

67 exc_value, _typer_developer_exception_attr_name, None 

68 ) 

69 standard_traceback = os.getenv("_TYPER_STANDARD_TRACEBACK") 1hceafbdg

70 if ( 1cabd

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

76 return 1hceafbdg

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

78 click_path = os.path.dirname(click.__file__) 1hceafbdg

79 supress_internal_dir_names = [typer_path, click_path] 1hceafbdg

80 exc = exc_value 1hceafbdg

81 if rich: 1hceafbdg

82 from .rich_utils import MAX_WIDTH 1hceafbdg

83 

84 rich_tb = Traceback.from_exception( 1hceafbdg

85 type(exc), 

86 exc, 

87 exc.__traceback__, 

88 show_locals=exception_config.pretty_exceptions_show_locals, 

89 suppress=supress_internal_dir_names, 

90 width=MAX_WIDTH, 

91 ) 

92 console_stderr.print(rich_tb) 1hceafbdg

93 return 1hceafbdg

94 tb_exc = traceback.TracebackException.from_exception(exc) 1hceafbdg

95 stack: List[FrameSummary] = [] 1hceafbdg

96 for frame in tb_exc.stack: 1hceafbdg

97 if any(frame.filename.startswith(path) for path in supress_internal_dir_names): 1hceafbdg

98 if not exception_config.pretty_exceptions_short: 1hceafbdg

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

100 stack.append( 1hceafbdg

101 traceback.FrameSummary( 

102 filename=frame.filename, 

103 lineno=frame.lineno, 

104 name=frame.name, 

105 line="", 

106 ) 

107 ) 

108 else: 

109 stack.append(frame) 1hceafbdg

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

111 final_stack_summary = StackSummary.from_list(stack) 1hceafbdg

112 tb_exc.stack = final_stack_summary 1hceafbdg

113 for line in tb_exc.format(): 1hceafbdg

114 print(line, file=sys.stderr) 1hceafbdg

115 return 1hceafbdg

116 

117 

118def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]: 1hceafbdg

119 install_param, show_param = get_completion_inspect_parameters() 1hceafbdg

120 click_install_param, _ = get_click_param(install_param) 1hceafbdg

121 click_show_param, _ = get_click_param(show_param) 1hceafbdg

122 return click_install_param, click_show_param 1hceafbdg

123 

124 

125class Typer: 1hceafbdg

126 def __init__( 1ceafbdg

127 self, 

128 *, 

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

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

131 invoke_without_command: bool = Default(False), 

132 no_args_is_help: bool = Default(False), 

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

134 chain: bool = Default(False), 

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

136 # Command 

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

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

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

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

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

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

143 add_help_option: bool = Default(True), 

144 hidden: bool = Default(False), 

145 deprecated: bool = Default(False), 

146 add_completion: bool = True, 

147 # Rich settings 

148 rich_markup_mode: MarkupMode = Default(DEFAULT_MARKUP_MODE), 

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

150 pretty_exceptions_enable: bool = True, 

151 pretty_exceptions_show_locals: bool = True, 

152 pretty_exceptions_short: bool = True, 

153 ): 

154 self._add_completion = add_completion 1hceafbdg

155 self.rich_markup_mode: MarkupMode = rich_markup_mode 1hceafbdg

156 self.rich_help_panel = rich_help_panel 1hceafbdg

157 self.pretty_exceptions_enable = pretty_exceptions_enable 1hceafbdg

158 self.pretty_exceptions_show_locals = pretty_exceptions_show_locals 1hceafbdg

159 self.pretty_exceptions_short = pretty_exceptions_short 1hceafbdg

160 self.info = TyperInfo( 1hceafbdg

161 name=name, 

162 cls=cls, 

163 invoke_without_command=invoke_without_command, 

164 no_args_is_help=no_args_is_help, 

165 subcommand_metavar=subcommand_metavar, 

166 chain=chain, 

167 result_callback=result_callback, 

168 context_settings=context_settings, 

169 callback=callback, 

170 help=help, 

171 epilog=epilog, 

172 short_help=short_help, 

173 options_metavar=options_metavar, 

174 add_help_option=add_help_option, 

175 hidden=hidden, 

176 deprecated=deprecated, 

177 ) 

178 self.registered_groups: List[TyperInfo] = [] 1hceafbdg

179 self.registered_commands: List[CommandInfo] = [] 1hceafbdg

180 self.registered_callback: Optional[TyperInfo] = None 1hceafbdg

181 

182 def callback( 1ceafbdg

183 self, 

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

185 *, 

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

187 invoke_without_command: bool = Default(False), 

188 no_args_is_help: bool = Default(False), 

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

190 chain: bool = Default(False), 

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

192 # Command 

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

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

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

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

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

198 add_help_option: bool = Default(True), 

199 hidden: bool = Default(False), 

200 deprecated: bool = Default(False), 

201 # Rich settings 

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

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

204 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1hceafbdg

205 self.registered_callback = TyperInfo( 1hceafbdg

206 name=name, 

207 cls=cls, 

208 invoke_without_command=invoke_without_command, 

209 no_args_is_help=no_args_is_help, 

210 subcommand_metavar=subcommand_metavar, 

211 chain=chain, 

212 result_callback=result_callback, 

213 context_settings=context_settings, 

214 callback=f, 

215 help=help, 

216 epilog=epilog, 

217 short_help=short_help, 

218 options_metavar=options_metavar, 

219 add_help_option=add_help_option, 

220 hidden=hidden, 

221 deprecated=deprecated, 

222 rich_help_panel=rich_help_panel, 

223 ) 

224 return f 1hceafbdg

225 

226 return decorator 1hceafbdg

227 

228 def command( 1ceafbdg

229 self, 

230 name: Optional[str] = None, 

231 *, 

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

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

234 help: Optional[str] = None, 

235 epilog: Optional[str] = None, 

236 short_help: Optional[str] = None, 

237 options_metavar: str = "[OPTIONS]", 

238 add_help_option: bool = True, 

239 no_args_is_help: bool = False, 

240 hidden: bool = False, 

241 deprecated: bool = False, 

242 # Rich settings 

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

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

245 if cls is None: 1hceafbdg

246 cls = TyperCommand 1hceafbdg

247 

248 def decorator(f: CommandFunctionType) -> CommandFunctionType: 1hceafbdg

249 self.registered_commands.append( 1hceafbdg

250 CommandInfo( 

251 name=name, 

252 cls=cls, 

253 context_settings=context_settings, 

254 callback=f, 

255 help=help, 

256 epilog=epilog, 

257 short_help=short_help, 

258 options_metavar=options_metavar, 

259 add_help_option=add_help_option, 

260 no_args_is_help=no_args_is_help, 

261 hidden=hidden, 

262 deprecated=deprecated, 

263 # Rich settings 

264 rich_help_panel=rich_help_panel, 

265 ) 

266 ) 

267 return f 1hceafbdg

268 

269 return decorator 1hceafbdg

270 

271 def add_typer( 1ceafbdg

272 self, 

273 typer_instance: "Typer", 

274 *, 

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

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

277 invoke_without_command: bool = Default(False), 

278 no_args_is_help: bool = Default(False), 

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

280 chain: bool = Default(False), 

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

282 # Command 

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

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

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

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

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

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

289 add_help_option: bool = Default(True), 

290 hidden: bool = Default(False), 

291 deprecated: bool = Default(False), 

292 # Rich settings 

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

294 ) -> None: 

295 self.registered_groups.append( 1hceafbdg

296 TyperInfo( 

297 typer_instance, 

298 name=name, 

299 cls=cls, 

300 invoke_without_command=invoke_without_command, 

301 no_args_is_help=no_args_is_help, 

302 subcommand_metavar=subcommand_metavar, 

303 chain=chain, 

304 result_callback=result_callback, 

305 context_settings=context_settings, 

306 callback=callback, 

307 help=help, 

308 epilog=epilog, 

309 short_help=short_help, 

310 options_metavar=options_metavar, 

311 add_help_option=add_help_option, 

312 hidden=hidden, 

313 deprecated=deprecated, 

314 rich_help_panel=rich_help_panel, 

315 ) 

316 ) 

317 

318 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1hceafbdg

319 if sys.excepthook != except_hook: 1hceafbdg

320 sys.excepthook = except_hook 1hceafbdg

321 try: 1hceafbdg

322 return get_command(self)(*args, **kwargs) 1hceafbdg

323 except Exception as e: 1hceafbdg

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

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

326 # raise custom_exc from e 

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

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

329 # actual error last. 

330 setattr( 1hceafbdg

331 e, 

332 _typer_developer_exception_attr_name, 

333 DeveloperExceptionConfig( 

334 pretty_exceptions_enable=self.pretty_exceptions_enable, 

335 pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, 

336 pretty_exceptions_short=self.pretty_exceptions_short, 

337 ), 

338 ) 

339 raise e 1hceafbdg

340 

341 

342def get_group(typer_instance: Typer) -> TyperGroup: 1hceafbdg

343 group = get_group_from_info( 1hceafbdg

344 TyperInfo(typer_instance), 

345 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

346 rich_markup_mode=typer_instance.rich_markup_mode, 

347 ) 

348 return group 1hceafbdg

349 

350 

351def get_command(typer_instance: Typer) -> click.Command: 1hceafbdg

352 if typer_instance._add_completion: 1hceafbdg

353 click_install_param, click_show_param = get_install_completion_arguments() 1hceafbdg

354 if ( 1cabd

355 typer_instance.registered_callback 

356 or typer_instance.info.callback 

357 or typer_instance.registered_groups 

358 or len(typer_instance.registered_commands) > 1 

359 ): 

360 # Create a Group 

361 click_command: click.Command = get_group(typer_instance) 1hceafbdg

362 if typer_instance._add_completion: 1hceafbdg

363 click_command.params.append(click_install_param) 1hceafbdg

364 click_command.params.append(click_show_param) 1hceafbdg

365 return click_command 1hceafbdg

366 elif len(typer_instance.registered_commands) == 1: 1hceafbdg

367 # Create a single Command 

368 single_command = typer_instance.registered_commands[0] 1hceafbdg

369 

370 if not single_command.context_settings and not isinstance( 1hceafbdg

371 typer_instance.info.context_settings, DefaultPlaceholder 

372 ): 

373 single_command.context_settings = typer_instance.info.context_settings 1hceafbdg

374 

375 click_command = get_command_from_info( 1hceafbdg

376 single_command, 

377 pretty_exceptions_short=typer_instance.pretty_exceptions_short, 

378 rich_markup_mode=typer_instance.rich_markup_mode, 

379 ) 

380 if typer_instance._add_completion: 1hceafbdg

381 click_command.params.append(click_install_param) 1hceafbdg

382 click_command.params.append(click_show_param) 1hceafbdg

383 return click_command 1hceafbdg

384 raise RuntimeError( 

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

386 ) # pragma: no cover 

387 

388 

389def get_group_name(typer_info: TyperInfo) -> Optional[str]: 1hceafbdg

390 if typer_info.callback: 1hceafbdg

391 # Priority 1: Callback passed in app.add_typer() 

392 return get_command_name(typer_info.callback.__name__) 1hceafbdg

393 if typer_info.typer_instance: 1hceafbdg

394 registered_callback = typer_info.typer_instance.registered_callback 1hceafbdg

395 if registered_callback: 1hceafbdg

396 if registered_callback.callback: 1hceafbdg

397 # Priority 2: Callback passed in @subapp.callback() 

398 return get_command_name(registered_callback.callback.__name__) 1hceafbdg

399 if typer_info.typer_instance.info.callback: 1hceafbdg

400 return get_command_name(typer_info.typer_instance.info.callback.__name__) 1hceafbdg

401 return None 1hceafbdg

402 

403 

404def solve_typer_info_help(typer_info: TyperInfo) -> str: 1hceafbdg

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

406 if not isinstance(typer_info.help, DefaultPlaceholder): 1hceafbdg

407 return inspect.cleandoc(typer_info.help or "") 1hceafbdg

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

409 try: 1hceafbdg

410 callback_help = typer_info.typer_instance.registered_callback.help 1hceafbdg

411 if not isinstance(callback_help, DefaultPlaceholder): 1hceafbdg

412 return inspect.cleandoc(callback_help or "") 1hceafbdg

413 except AttributeError: 1hceafbdg

414 pass 1hceafbdg

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

416 try: 1hceafbdg

417 instance_help = typer_info.typer_instance.info.help 1hceafbdg

418 if not isinstance(instance_help, DefaultPlaceholder): 1hceafbdg

419 return inspect.cleandoc(instance_help or "") 1hceafbdg

420 except AttributeError: 1hceafbdg

421 pass 1hceafbdg

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

423 if typer_info.callback: 1hceafbdg

424 doc = inspect.getdoc(typer_info.callback) 1hceafbdg

425 if doc: 1hceafbdg

426 return doc 1hceafbdg

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

428 try: 1hceafbdg

429 callback = typer_info.typer_instance.registered_callback.callback 1hceafbdg

430 if not isinstance(callback, DefaultPlaceholder): 1hceafbdg

431 doc = inspect.getdoc(callback or "") 1hceafbdg

432 if doc: 1hceafbdg

433 return doc 1hceafbdg

434 except AttributeError: 1hceafbdg

435 pass 1hceafbdg

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

437 try: 1hceafbdg

438 instance_callback = typer_info.typer_instance.info.callback 1hceafbdg

439 if not isinstance(instance_callback, DefaultPlaceholder): 1hceafbdg

440 doc = inspect.getdoc(instance_callback) 1hceafbdg

441 if doc: 1hceafbdg

442 return doc 1hceafbdg

443 except AttributeError: 1hceafbdg

444 pass 1hceafbdg

445 # Value not set, use the default 

446 return typer_info.help.value 1hceafbdg

447 

448 

449def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: 1hceafbdg

450 values: Dict[str, Any] = {} 1hceafbdg

451 for name, value in typer_info.__dict__.items(): 1hceafbdg

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

453 if not isinstance(value, DefaultPlaceholder): 1hceafbdg

454 values[name] = value 1hceafbdg

455 continue 1hceafbdg

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

457 try: 1hceafbdg

458 callback_value = getattr( 1hceafbdg

459 typer_info.typer_instance.registered_callback, # type: ignore 

460 name, 

461 ) 

462 if not isinstance(callback_value, DefaultPlaceholder): 1hceafbdg

463 values[name] = callback_value 1hceafbdg

464 continue 1hceafbdg

465 except AttributeError: 1hceafbdg

466 pass 1hceafbdg

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

468 try: 1hceafbdg

469 instance_value = getattr( 1hceafbdg

470 typer_info.typer_instance.info, # type: ignore 

471 name, 

472 ) 

473 if not isinstance(instance_value, DefaultPlaceholder): 1hceafbdg

474 values[name] = instance_value 1hceafbdg

475 continue 1hceafbdg

476 except AttributeError: 1hceafbdg

477 pass 1hceafbdg

478 # Value not set, use the default 

479 values[name] = value.value 1hceafbdg

480 if values["name"] is None: 1hceafbdg

481 values["name"] = get_group_name(typer_info) 1hceafbdg

482 values["help"] = solve_typer_info_help(typer_info) 1hceafbdg

483 return TyperInfo(**values) 1hceafbdg

484 

485 

486def get_group_from_info( 1ceafbdg

487 group_info: TyperInfo, 

488 *, 

489 pretty_exceptions_short: bool, 

490 rich_markup_mode: MarkupMode, 

491) -> TyperGroup: 

492 assert ( 1cabd

493 group_info.typer_instance 

494 ), "A Typer instance is needed to generate a Click Group" 

495 commands: Dict[str, click.Command] = {} 1hceafbdg

496 for command_info in group_info.typer_instance.registered_commands: 1hceafbdg

497 command = get_command_from_info( 1hceafbdg

498 command_info=command_info, 

499 pretty_exceptions_short=pretty_exceptions_short, 

500 rich_markup_mode=rich_markup_mode, 

501 ) 

502 if command.name: 1hceafbdg

503 commands[command.name] = command 1hceafbdg

504 for sub_group_info in group_info.typer_instance.registered_groups: 1hceafbdg

505 sub_group = get_group_from_info( 1hceafbdg

506 sub_group_info, 

507 pretty_exceptions_short=pretty_exceptions_short, 

508 rich_markup_mode=rich_markup_mode, 

509 ) 

510 if sub_group.name: 1hceafbdg

511 commands[sub_group.name] = sub_group 1hceafbdg

512 solved_info = solve_typer_info_defaults(group_info) 1hceafbdg

513 ( 1ceafbdg

514 params, 

515 convertors, 

516 context_param_name, 

517 ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) 

518 cls = solved_info.cls or TyperGroup 1hceafbdg

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

520 group = cls( 1hceafbdg

521 name=solved_info.name or "", 

522 commands=commands, 

523 invoke_without_command=solved_info.invoke_without_command, 

524 no_args_is_help=solved_info.no_args_is_help, 

525 subcommand_metavar=solved_info.subcommand_metavar, 

526 chain=solved_info.chain, 

527 result_callback=solved_info.result_callback, 

528 context_settings=solved_info.context_settings, 

529 callback=get_callback( 

530 callback=solved_info.callback, 

531 params=params, 

532 convertors=convertors, 

533 context_param_name=context_param_name, 

534 pretty_exceptions_short=pretty_exceptions_short, 

535 ), 

536 params=params, 

537 help=solved_info.help, 

538 epilog=solved_info.epilog, 

539 short_help=solved_info.short_help, 

540 options_metavar=solved_info.options_metavar, 

541 add_help_option=solved_info.add_help_option, 

542 hidden=solved_info.hidden, 

543 deprecated=solved_info.deprecated, 

544 rich_markup_mode=rich_markup_mode, 

545 # Rich settings 

546 rich_help_panel=solved_info.rich_help_panel, 

547 ) 

548 return group 1hceafbdg

549 

550 

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

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

553 

554 

555def get_params_convertors_ctx_param_name_from_function( 1ceafbdg

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

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

558 params = [] 1hceafbdg

559 convertors = {} 1hceafbdg

560 context_param_name = None 1hceafbdg

561 if callback: 1hceafbdg

562 parameters = get_params_from_function(callback) 1hceafbdg

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

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

565 context_param_name = param_name 1hceafbdg

566 continue 1hceafbdg

567 click_param, convertor = get_click_param(param) 1hceafbdg

568 if convertor: 1hceafbdg

569 convertors[param_name] = convertor 1hceafbdg

570 params.append(click_param) 1hceafbdg

571 return params, convertors, context_param_name 1hceafbdg

572 

573 

574def get_command_from_info( 1ceafbdg

575 command_info: CommandInfo, 

576 *, 

577 pretty_exceptions_short: bool, 

578 rich_markup_mode: MarkupMode, 

579) -> click.Command: 

580 assert command_info.callback, "A command must have a callback function" 1hceafbdg

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

582 use_help = command_info.help 1hceafbdg

583 if use_help is None: 1hceafbdg

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

585 else: 

586 use_help = inspect.cleandoc(use_help) 1hceafbdg

587 ( 1ceafbdg

588 params, 

589 convertors, 

590 context_param_name, 

591 ) = get_params_convertors_ctx_param_name_from_function(command_info.callback) 

592 cls = command_info.cls or TyperCommand 1hceafbdg

593 command = cls( 1hceafbdg

594 name=name, 

595 context_settings=command_info.context_settings, 

596 callback=get_callback( 

597 callback=command_info.callback, 

598 params=params, 

599 convertors=convertors, 

600 context_param_name=context_param_name, 

601 pretty_exceptions_short=pretty_exceptions_short, 

602 ), 

603 params=params, # type: ignore 

604 help=use_help, 

605 epilog=command_info.epilog, 

606 short_help=command_info.short_help, 

607 options_metavar=command_info.options_metavar, 

608 add_help_option=command_info.add_help_option, 

609 no_args_is_help=command_info.no_args_is_help, 

610 hidden=command_info.hidden, 

611 deprecated=command_info.deprecated, 

612 rich_markup_mode=rich_markup_mode, 

613 # Rich settings 

614 rich_help_panel=command_info.rich_help_panel, 

615 ) 

616 return command 1hceafbdg

617 

618 

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

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

621 if lenient_issubclass(type_, Path): 1hceafbdg

622 convertor = param_path_convertor 1hceafbdg

623 if lenient_issubclass(type_, Enum): 1hceafbdg

624 convertor = generate_enum_convertor(type_) 1hceafbdg

625 return convertor 1hceafbdg

626 

627 

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

629 if value is not None: 1hceafbdg

630 return Path(value) 1hceafbdg

631 return None 1hceafbdg

632 

633 

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

635 val_map = {str(val.value): val for val in enum} 1hceafbdg

636 

637 def convertor(value: Any) -> Any: 1hceafbdg

638 if value is not None: 1hceafbdg

639 val = str(value) 1hceafbdg

640 if val in val_map: 1hceafbdg

641 key = val_map[val] 1hceafbdg

642 return enum(key) 1hceafbdg

643 

644 return convertor 1hceafbdg

645 

646 

647def generate_list_convertor( 1ceafbdg

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

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

650 def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]: 1hceafbdg

651 if default_value is None and len(value) == 0: 1hceafbdg

652 return None 1hceafbdg

653 return [convertor(v) if convertor else v for v in value] 1hceafbdg

654 

655 return internal_convertor 1hceafbdg

656 

657 

658def generate_tuple_convertor( 1ceafbdg

659 types: Sequence[Any], 

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

661 convertors = [determine_type_convertor(type_) for type_ in types] 1hceafbdg

662 

663 def internal_convertor( 1ceafbdg

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

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

666 if param_args is None: 1hceafbdg

667 return None 1hceafbdg

668 return tuple( 1hceafbdg

669 convertor(arg) if convertor else arg 

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

671 ) 

672 

673 return internal_convertor 1hceafbdg

674 

675 

676def get_callback( 1ceafbdg

677 *, 

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

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

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

681 context_param_name: Optional[str] = None, 

682 pretty_exceptions_short: bool, 

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

684 use_convertors = convertors or {} 1hceafbdg

685 if not callback: 1hceafbdg

686 return None 1hceafbdg

687 parameters = get_params_from_function(callback) 1hceafbdg

688 use_params: Dict[str, Any] = {} 1hceafbdg

689 for param_name in parameters: 1hceafbdg

690 use_params[param_name] = None 1hceafbdg

691 for param in params: 1hceafbdg

692 if param.name: 1hceafbdg

693 use_params[param.name] = param.default 1hceafbdg

694 

695 def wrapper(**kwargs: Any) -> Any: 1hceafbdg

696 _rich_traceback_guard = pretty_exceptions_short # noqa: F841 1hceafbdg

697 for k, v in kwargs.items(): 1hceafbdg

698 if k in use_convertors: 1hceafbdg

699 use_params[k] = use_convertors[k](v) 1hceafbdg

700 else: 

701 use_params[k] = v 1hceafbdg

702 if context_param_name: 1hceafbdg

703 use_params[context_param_name] = click.get_current_context() 1hceafbdg

704 return callback(**use_params) 1hceafbdg

705 

706 update_wrapper(wrapper, callback) 1hceafbdg

707 return wrapper 1hceafbdg

708 

709 

710def get_click_type( 1ceafbdg

711 *, annotation: Any, parameter_info: ParameterInfo 

712) -> click.ParamType: 

713 if parameter_info.click_type is not None: 1hceafbdg

714 return parameter_info.click_type 1hceafbdg

715 

716 elif parameter_info.parser is not None: 1hceafbdg

717 return click.types.FuncParamType(parameter_info.parser) 1hceafbdg

718 

719 elif annotation is str: 1hceafbdg

720 return click.STRING 1hceafbdg

721 elif annotation is int: 1hceafbdg

722 if parameter_info.min is not None or parameter_info.max is not None: 1hceafbdg

723 min_ = None 1hceafbdg

724 max_ = None 1hceafbdg

725 if parameter_info.min is not None: 1hceafbdg

726 min_ = int(parameter_info.min) 1hceafbdg

727 if parameter_info.max is not None: 1hceafbdg

728 max_ = int(parameter_info.max) 1hceafbdg

729 return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) 1hceafbdg

730 else: 

731 return click.INT 1hceafbdg

732 elif annotation is float: 1hceafbdg

733 if parameter_info.min is not None or parameter_info.max is not None: 1hceafbdg

734 return click.FloatRange( 1hceafbdg

735 min=parameter_info.min, 

736 max=parameter_info.max, 

737 clamp=parameter_info.clamp, 

738 ) 

739 else: 

740 return click.FLOAT 1hceafbdg

741 elif annotation is bool: 1hceafbdg

742 return click.BOOL 1hceafbdg

743 elif annotation == UUID: 1hceafbdg

744 return click.UUID 1hceafbdg

745 elif annotation == datetime: 1hceafbdg

746 return click.DateTime(formats=parameter_info.formats) 1hceafbdg

747 elif ( 1ab

748 annotation == Path 

749 or parameter_info.allow_dash 

750 or parameter_info.path_type 

751 or parameter_info.resolve_path 

752 ): 

753 return click.Path( 1hceafbdg

754 exists=parameter_info.exists, 

755 file_okay=parameter_info.file_okay, 

756 dir_okay=parameter_info.dir_okay, 

757 writable=parameter_info.writable, 

758 readable=parameter_info.readable, 

759 resolve_path=parameter_info.resolve_path, 

760 allow_dash=parameter_info.allow_dash, 

761 path_type=parameter_info.path_type, 

762 ) 

763 elif lenient_issubclass(annotation, FileTextWrite): 1hceafbdg

764 return click.File( 1hceafbdg

765 mode=parameter_info.mode or "w", 

766 encoding=parameter_info.encoding, 

767 errors=parameter_info.errors, 

768 lazy=parameter_info.lazy, 

769 atomic=parameter_info.atomic, 

770 ) 

771 elif lenient_issubclass(annotation, FileText): 1hceafbdg

772 return click.File( 1hceafbdg

773 mode=parameter_info.mode or "r", 

774 encoding=parameter_info.encoding, 

775 errors=parameter_info.errors, 

776 lazy=parameter_info.lazy, 

777 atomic=parameter_info.atomic, 

778 ) 

779 elif lenient_issubclass(annotation, FileBinaryRead): 1hceafbdg

780 return click.File( 1hceafbdg

781 mode=parameter_info.mode or "rb", 

782 encoding=parameter_info.encoding, 

783 errors=parameter_info.errors, 

784 lazy=parameter_info.lazy, 

785 atomic=parameter_info.atomic, 

786 ) 

787 elif lenient_issubclass(annotation, FileBinaryWrite): 1hceafbdg

788 return click.File( 1hceafbdg

789 mode=parameter_info.mode or "wb", 

790 encoding=parameter_info.encoding, 

791 errors=parameter_info.errors, 

792 lazy=parameter_info.lazy, 

793 atomic=parameter_info.atomic, 

794 ) 

795 elif lenient_issubclass(annotation, Enum): 1hceafbdg

796 return click.Choice( 1hceafbdg

797 [item.value for item in annotation], 

798 case_sensitive=parameter_info.case_sensitive, 

799 ) 

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

801 

802 

803def lenient_issubclass( 1ceafbdg

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

805) -> bool: 

806 return isinstance(cls, type) and issubclass(cls, class_or_tuple) 1hceafbdg

807 

808 

809def get_click_param( 1ceafbdg

810 param: ParamMeta, 

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

812 # First, find out what will be: 

813 # * ParamInfo (ArgumentInfo or OptionInfo) 

814 # * default_value 

815 # * required 

816 default_value = None 1hceafbdg

817 required = False 1hceafbdg

818 if isinstance(param.default, ParameterInfo): 1hceafbdg

819 parameter_info = param.default 1hceafbdg

820 if parameter_info.default == Required: 1hceafbdg

821 required = True 1hceafbdg

822 else: 

823 default_value = parameter_info.default 1hceafbdg

824 elif param.default == Required or param.default == param.empty: 1hceafbdg

825 required = True 1hceafbdg

826 parameter_info = ArgumentInfo() 1hceafbdg

827 else: 

828 default_value = param.default 1hceafbdg

829 parameter_info = OptionInfo() 1hceafbdg

830 annotation: Any 

831 if not param.annotation == param.empty: 1hceafbdg

832 annotation = param.annotation 1hceafbdg

833 else: 

834 annotation = str 1hceafbdg

835 main_type = annotation 1hceafbdg

836 is_list = False 1hceafbdg

837 is_tuple = False 1hceafbdg

838 parameter_type: Any = None 1hceafbdg

839 is_flag = None 1hceafbdg

840 origin = get_origin(main_type) 1hceafbdg

841 

842 if origin is not None: 1hceafbdg

843 # Handle SomeType | None and Optional[SomeType] 

844 if is_union(origin): 1hceafbdg

845 types = [] 1hceafbdg

846 for type_ in get_args(main_type): 1hceafbdg

847 if type_ is NoneType: 1hceafbdg

848 continue 1hceafbdg

849 types.append(type_) 1hceafbdg

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

851 main_type = types[0] 1hceafbdg

852 origin = get_origin(main_type) 1hceafbdg

853 # Handle Tuples and Lists 

854 if lenient_issubclass(origin, List): 1hceafbdg

855 main_type = get_args(main_type)[0] 1hceafbdg

856 assert not get_origin( 1hceafbdg

857 main_type 

858 ), "List types with complex sub-types are not currently supported" 

859 is_list = True 1hceafbdg

860 elif lenient_issubclass(origin, Tuple): # type: ignore 1hceafbdg

861 types = [] 1hceafbdg

862 for type_ in get_args(main_type): 1hceafbdg

863 assert not get_origin( 1hceafbdg

864 type_ 

865 ), "Tuple types with complex sub-types are not currently supported" 

866 types.append( 1hceafbdg

867 get_click_type(annotation=type_, parameter_info=parameter_info) 

868 ) 

869 parameter_type = tuple(types) 1hceafbdg

870 is_tuple = True 1hceafbdg

871 if parameter_type is None: 1hceafbdg

872 parameter_type = get_click_type( 1hceafbdg

873 annotation=main_type, parameter_info=parameter_info 

874 ) 

875 convertor = determine_type_convertor(main_type) 1hceafbdg

876 if is_list: 1hceafbdg

877 convertor = generate_list_convertor( 1hceafbdg

878 convertor=convertor, default_value=default_value 

879 ) 

880 if is_tuple: 1hceafbdg

881 convertor = generate_tuple_convertor(get_args(main_type)) 1hceafbdg

882 if isinstance(parameter_info, OptionInfo): 1hceafbdg

883 if main_type is bool and parameter_info.is_flag is not False: 1hceafbdg

884 is_flag = True 1hceafbdg

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

886 # to bool internally 

887 parameter_type = None 1hceafbdg

888 default_option_name = get_command_name(param.name) 1hceafbdg

889 if is_flag: 1hceafbdg

890 default_option_declaration = ( 1ceafbdg

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

892 ) 

893 else: 

894 default_option_declaration = f"--{default_option_name}" 1hceafbdg

895 param_decls = [param.name] 1hceafbdg

896 if parameter_info.param_decls: 1hceafbdg

897 param_decls.extend(parameter_info.param_decls) 1hceafbdg

898 else: 

899 param_decls.append(default_option_declaration) 1hceafbdg

900 return ( 1ceafbdg

901 TyperOption( 

902 # Option 

903 param_decls=param_decls, 

904 show_default=parameter_info.show_default, 

905 prompt=parameter_info.prompt, 

906 confirmation_prompt=parameter_info.confirmation_prompt, 

907 prompt_required=parameter_info.prompt_required, 

908 hide_input=parameter_info.hide_input, 

909 is_flag=is_flag, 

910 flag_value=parameter_info.flag_value, 

911 multiple=is_list, 

912 count=parameter_info.count, 

913 allow_from_autoenv=parameter_info.allow_from_autoenv, 

914 type=parameter_type, 

915 help=parameter_info.help, 

916 hidden=parameter_info.hidden, 

917 show_choices=parameter_info.show_choices, 

918 show_envvar=parameter_info.show_envvar, 

919 # Parameter 

920 required=required, 

921 default=default_value, 

922 callback=get_param_callback( 

923 callback=parameter_info.callback, convertor=convertor 

924 ), 

925 metavar=parameter_info.metavar, 

926 expose_value=parameter_info.expose_value, 

927 is_eager=parameter_info.is_eager, 

928 envvar=parameter_info.envvar, 

929 shell_complete=parameter_info.shell_complete, 

930 autocompletion=get_param_completion(parameter_info.autocompletion), 

931 # Rich settings 

932 rich_help_panel=parameter_info.rich_help_panel, 

933 ), 

934 convertor, 

935 ) 

936 elif isinstance(parameter_info, ArgumentInfo): 1hceafbdg

937 param_decls = [param.name] 1hceafbdg

938 nargs = None 1hceafbdg

939 if is_list: 1hceafbdg

940 nargs = -1 1hceafbdg

941 return ( 1ceafbdg

942 TyperArgument( 

943 # Argument 

944 param_decls=param_decls, 

945 type=parameter_type, 

946 required=required, 

947 nargs=nargs, 

948 # TyperArgument 

949 show_default=parameter_info.show_default, 

950 show_choices=parameter_info.show_choices, 

951 show_envvar=parameter_info.show_envvar, 

952 help=parameter_info.help, 

953 hidden=parameter_info.hidden, 

954 # Parameter 

955 default=default_value, 

956 callback=get_param_callback( 

957 callback=parameter_info.callback, convertor=convertor 

958 ), 

959 metavar=parameter_info.metavar, 

960 expose_value=parameter_info.expose_value, 

961 is_eager=parameter_info.is_eager, 

962 envvar=parameter_info.envvar, 

963 shell_complete=parameter_info.shell_complete, 

964 autocompletion=get_param_completion(parameter_info.autocompletion), 

965 # Rich settings 

966 rich_help_panel=parameter_info.rich_help_panel, 

967 ), 

968 convertor, 

969 ) 

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

971 

972 

973def get_param_callback( 1ceafbdg

974 *, 

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

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

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

978 if not callback: 1hceafbdg

979 return None 1hceafbdg

980 parameters = get_params_from_function(callback) 1hceafbdg

981 ctx_name = None 1hceafbdg

982 click_param_name = None 1hceafbdg

983 value_name = None 1hceafbdg

984 untyped_names: List[str] = [] 1hceafbdg

985 for param_name, param_sig in parameters.items(): 1hceafbdg

986 if lenient_issubclass(param_sig.annotation, click.Context): 1hceafbdg

987 ctx_name = param_name 1hceafbdg

988 elif lenient_issubclass(param_sig.annotation, click.Parameter): 1hceafbdg

989 click_param_name = param_name 1hceafbdg

990 else: 

991 untyped_names.append(param_name) 1hceafbdg

992 # Extract value param name first 

993 if untyped_names: 1hceafbdg

994 value_name = untyped_names.pop() 1hceafbdg

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

996 if untyped_names: 1hceafbdg

997 if ctx_name is None: 1hceafbdg

998 ctx_name = untyped_names.pop(0) 1hceafbdg

999 if click_param_name is None: 1hceafbdg

1000 if untyped_names: 1hceafbdg

1001 click_param_name = untyped_names.pop(0) 1hceafbdg

1002 if untyped_names: 1hceafbdg

1003 raise click.ClickException( 1hceafbdg

1004 "Too many CLI parameter callback function parameters" 

1005 ) 

1006 

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

1008 use_params: Dict[str, Any] = {} 1hceafbdg

1009 if ctx_name: 1hceafbdg

1010 use_params[ctx_name] = ctx 1hceafbdg

1011 if click_param_name: 1hceafbdg

1012 use_params[click_param_name] = param 1hceafbdg

1013 if value_name: 1hceafbdg

1014 if convertor: 1hceafbdg

1015 use_value = convertor(value) 1hceafbdg

1016 else: 

1017 use_value = value 1hceafbdg

1018 use_params[value_name] = use_value 1hceafbdg

1019 return callback(**use_params) 1hceafbdg

1020 

1021 update_wrapper(wrapper, callback) 1hceafbdg

1022 return wrapper 1hceafbdg

1023 

1024 

1025def get_param_completion( 1ceafbdg

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

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

1028 if not callback: 1hceafbdg

1029 return None 1hceafbdg

1030 parameters = get_params_from_function(callback) 1hceafbdg

1031 ctx_name = None 1hceafbdg

1032 args_name = None 1hceafbdg

1033 incomplete_name = None 1hceafbdg

1034 unassigned_params = list(parameters.values()) 1hceafbdg

1035 for param_sig in unassigned_params[:]: 1hceafbdg

1036 origin = get_origin(param_sig.annotation) 1hceafbdg

1037 if lenient_issubclass(param_sig.annotation, click.Context): 1hceafbdg

1038 ctx_name = param_sig.name 1hceafbdg

1039 unassigned_params.remove(param_sig) 1hceafbdg

1040 elif lenient_issubclass(origin, List): 1hceafbdg

1041 args_name = param_sig.name 1hceafbdg

1042 unassigned_params.remove(param_sig) 1hceafbdg

1043 elif lenient_issubclass(param_sig.annotation, str): 1hceafbdg

1044 incomplete_name = param_sig.name 1hceafbdg

1045 unassigned_params.remove(param_sig) 1hceafbdg

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

1047 for param_sig in unassigned_params[:]: 1hceafbdg

1048 if ctx_name is None and param_sig.name == "ctx": 1hceafbdg

1049 ctx_name = param_sig.name 1hceafbdg

1050 unassigned_params.remove(param_sig) 1hceafbdg

1051 elif args_name is None and param_sig.name == "args": 1hceafbdg

1052 args_name = param_sig.name 1hceafbdg

1053 unassigned_params.remove(param_sig) 1hceafbdg

1054 elif incomplete_name is None and param_sig.name == "incomplete": 1hceafbdg

1055 incomplete_name = param_sig.name 1hceafbdg

1056 unassigned_params.remove(param_sig) 1hceafbdg

1057 # Extract value param name first 

1058 if unassigned_params: 1hceafbdg

1059 show_params = " ".join([param.name for param in unassigned_params]) 1hceafbdg

1060 raise click.ClickException( 1hceafbdg

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

1062 ) 

1063 

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

1065 use_params: Dict[str, Any] = {} 1hceafbdg

1066 if ctx_name: 1hceafbdg

1067 use_params[ctx_name] = ctx 1hceafbdg

1068 if args_name: 1hceafbdg

1069 use_params[args_name] = args 1hceafbdg

1070 if incomplete_name: 1hceafbdg

1071 use_params[incomplete_name] = incomplete 1hceafbdg

1072 return callback(**use_params) 1hceafbdg

1073 

1074 update_wrapper(wrapper, callback) 1hceafbdg

1075 return wrapper 1hceafbdg

1076 

1077 

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

1079 app = Typer(add_completion=False) 1hceafbdg

1080 app.command()(function) 1hceafbdg

1081 app() 1hceafbdg