Coverage for typer / core.py: 100%

329 statements  

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

1import errno 1bfgadech

2import importlib.util 1bfgadech

3import inspect 1bfgadech

4import os 1bfgadech

5import sys 1bfgadech

6from collections.abc import MutableMapping, Sequence 1bfgadech

7from difflib import get_close_matches 1bfgadech

8from enum import Enum 1bfgadech

9from gettext import gettext as _ 1bfgadech

10from typing import ( 1bfgadech

11 Any, 

12 Callable, 

13 Optional, 

14 TextIO, 

15 Union, 

16 cast, 

17) 

18 

19import click 1bfgadech

20import click.core 1bfgadech

21import click.formatting 1bfgadech

22import click.shell_completion 1bfgadech

23import click.types 1bfgadech

24import click.utils 1bfgadech

25 

26from ._typing import Literal 1bfgadech

27 

28MarkupMode = Literal["markdown", "rich", None] 1bfgadech

29MARKUP_MODE_KEY = "TYPER_RICH_MARKUP_MODE" 1bfgadech

30 

31HAS_RICH = importlib.util.find_spec("rich") is not None 1bfgadech

32HAS_SHELLINGHAM = importlib.util.find_spec("shellingham") is not None 1bfgadech

33 

34if HAS_RICH: 1bfgadech

35 DEFAULT_MARKUP_MODE: MarkupMode = "rich" 1bfgadech

36else: # pragma: no cover 

37 DEFAULT_MARKUP_MODE = None 

38 

39 

40# Copy from click.parser._split_opt 

41def _split_opt(opt: str) -> tuple[str, str]: 1bfgadech

42 first = opt[:1] 1bfgadech

43 if first.isalnum(): 1bfgadech

44 return "", opt 1bfgadech

45 if opt[1:2] == first: 1bfgadech

46 return opt[:2], opt[2:] 1bfgadech

47 return first, opt[1:] 1bfgadech

48 

49 

50def _typer_param_setup_autocompletion_compat( 1bfgadech

51 self: click.Parameter, 

52 *, 

53 autocompletion: Optional[ 

54 Callable[[click.Context, list[str], str], list[Union[tuple[str, str], str]]] 

55 ] = None, 

56) -> None: 

57 if self._custom_shell_complete is not None: 1bfgadech

58 import warnings 1bfgadech

59 

60 warnings.warn( 1bfgadech

61 "In Typer, only the parameter 'autocompletion' is supported. " 

62 "The support for 'shell_complete' is deprecated and will be removed in upcoming versions. ", 

63 DeprecationWarning, 

64 stacklevel=2, 

65 ) 

66 

67 if autocompletion is not None: 1bfgadech

68 

69 def compat_autocompletion( 1bfgadech

70 ctx: click.Context, param: click.core.Parameter, incomplete: str 

71 ) -> list["click.shell_completion.CompletionItem"]: 

72 from click.shell_completion import CompletionItem 1bfgadech

73 

74 out = [] 1bfgadech

75 

76 for c in autocompletion(ctx, [], incomplete): 1bfgadech

77 if isinstance(c, tuple): 1bfgadech

78 use_completion = CompletionItem(c[0], help=c[1]) 1bfgadech

79 else: 

80 assert isinstance(c, str) 1bfgadech

81 use_completion = CompletionItem(c) 1bfgadech

82 

83 if use_completion.value.startswith(incomplete): 1bfgadech

84 out.append(use_completion) 1bfgadech

85 

86 return out 1bfgadech

87 

88 self._custom_shell_complete = compat_autocompletion 1bfgadech

89 

90 

91def _get_default_string( 1bfgadech

92 obj: Union["TyperArgument", "TyperOption"], 

93 *, 

94 ctx: click.Context, 

95 show_default_is_str: bool, 

96 default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any], 

97) -> str: 

98 # Extracted from click.core.Option.get_help_record() to be reused by 

99 # rich_utils avoiding RegEx hacks 

100 if show_default_is_str: 1bfgadech

101 default_string = f"({obj.show_default})" 1bfgadech

102 elif isinstance(default_value, (list, tuple)): 1bfgadech

103 default_string = ", ".join( 1bfgadech

104 _get_default_string( 

105 obj, ctx=ctx, show_default_is_str=show_default_is_str, default_value=d 

106 ) 

107 for d in default_value 

108 ) 

109 elif isinstance(default_value, Enum): 1bfgadech

110 default_string = str(default_value.value) 1bfgadech

111 elif inspect.isfunction(default_value): 1bfgadech

112 default_string = _("(dynamic)") 1bfgadech

113 elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: 1bfgadech

114 # For boolean flags that have distinct True/False opts, 

115 # use the opt without prefix instead of the value. 

116 # Typer override, original commented 

117 # default_string = click.parser.split_opt( 

118 # (self.opts if self.default else self.secondary_opts)[0] 

119 # )[1] 

120 if obj.default: 1bfgadech

121 if obj.opts: 1bfgadech

122 default_string = _split_opt(obj.opts[0])[1] 1bfgadech

123 else: 

124 default_string = str(default_value) 1bfgadech

125 else: 

126 default_string = _split_opt(obj.secondary_opts[0])[1] 1bfgadech

127 # Typer override end 

128 elif ( 1bac

129 isinstance(obj, TyperOption) 

130 and obj.is_bool_flag 

131 and not obj.secondary_opts 

132 and not default_value 

133 ): 

134 default_string = "" 1bfgadech

135 else: 

136 default_string = str(default_value) 1bfgadech

137 return default_string 1bfgadech

138 

139 

140def _extract_default_help_str( 1bfgadech

141 obj: Union["TyperArgument", "TyperOption"], *, ctx: click.Context 

142) -> Optional[Union[Any, Callable[[], Any]]]: 

143 # Extracted from click.core.Option.get_help_record() to be reused by 

144 # rich_utils avoiding RegEx hacks 

145 # Temporarily enable resilient parsing to avoid type casting 

146 # failing for the default. Might be possible to extend this to 

147 # help formatting in general. 

148 resilient = ctx.resilient_parsing 1bfgadech

149 ctx.resilient_parsing = True 1bfgadech

150 

151 try: 1bfgadech

152 default_value = obj.get_default(ctx, call=False) 1bfgadech

153 finally: 

154 ctx.resilient_parsing = resilient 1bfgadech

155 return default_value 1bfgadech

156 

157 

158def _main( 1bfgadech

159 self: click.Command, 

160 *, 

161 args: Optional[Sequence[str]] = None, 

162 prog_name: Optional[str] = None, 

163 complete_var: Optional[str] = None, 

164 standalone_mode: bool = True, 

165 windows_expand_args: bool = True, 

166 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

167 **extra: Any, 

168) -> Any: 

169 # Typer override, duplicated from click.main() to handle custom rich exceptions 

170 # Verify that the environment is configured correctly, or reject 

171 # further execution to avoid a broken script. 

172 if args is None: 1bfgadech

173 args = sys.argv[1:] 1bfgadech

174 

175 # Covered in Click tests 

176 if os.name == "nt" and windows_expand_args: # pragma: no cover 1bfgadech

177 args = click.utils._expand_args(args) 1ade

178 else: 

179 args = list(args) 1bfgadech

180 

181 if prog_name is None: 1bfgadech

182 prog_name = click.utils._detect_program_name() 1bfgadech

183 

184 # Process shell completion requests and exit early. 

185 self._main_shell_completion(extra, prog_name, complete_var) 1bfgadech

186 

187 try: 1bfgadech

188 try: 1bfgadech

189 with self.make_context(prog_name, args, **extra) as ctx: 1bfgadech

190 rv = self.invoke(ctx) 1bfgadech

191 if not standalone_mode: 1bfgadech

192 return rv 1bfgadech

193 # it's not safe to `ctx.exit(rv)` here! 

194 # note that `rv` may actually contain data like "1" which 

195 # has obvious effects 

196 # more subtle case: `rv=[None, None]` can come out of 

197 # chained commands which all returned `None` -- so it's not 

198 # even always obvious that `rv` indicates success/failure 

199 # by its truthiness/falsiness 

200 ctx.exit() 1bfgadech

201 except EOFError as e: 1bfgadech

202 click.echo(file=sys.stderr) 1bfgadech

203 raise click.Abort() from e 1bfgadech

204 except KeyboardInterrupt as e: 1bfgadech

205 raise click.exceptions.Exit(130) from e 1bfgadech

206 except click.ClickException as e: 1bfgadech

207 if not standalone_mode: 1bfgadech

208 raise 1bfgadech

209 # Typer override 

210 if HAS_RICH and rich_markup_mode is not None: 1bfgadech

211 from . import rich_utils 1bfgadech

212 

213 rich_utils.rich_format_error(e) 1bfgadech

214 else: 

215 e.show() 1bfgadech

216 # Typer override end 

217 sys.exit(e.exit_code) 1bfgadech

218 except OSError as e: 1bfgadech

219 if e.errno == errno.EPIPE: 1bfgadech

220 sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout)) 1bfgadech

221 sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr)) 1bfgadech

222 sys.exit(1) 1bfgadech

223 else: 

224 raise 1bfgadech

225 except click.exceptions.Exit as e: 1bfgadech

226 if standalone_mode: 1bfgadech

227 sys.exit(e.exit_code) 1bfgadech

228 else: 

229 # in non-standalone mode, return the exit code 

230 # note that this is only reached if `self.invoke` above raises 

231 # an Exit explicitly -- thus bypassing the check there which 

232 # would return its result 

233 # the results of non-standalone execution may therefore be 

234 # somewhat ambiguous: if there are codepaths which lead to 

235 # `ctx.exit(1)` and to `return 1`, the caller won't be able to 

236 # tell the difference between the two 

237 return e.exit_code 1bfgadech

238 except click.Abort: 1bfgadech

239 if not standalone_mode: 1bfgadech

240 raise 1bfgadech

241 # Typer override 

242 if HAS_RICH and rich_markup_mode is not None: 1bfgadech

243 from . import rich_utils 1bfgadech

244 

245 rich_utils.rich_abort_error() 1bfgadech

246 else: 

247 click.echo(_("Aborted!"), file=sys.stderr) 1bfgadech

248 # Typer override end 

249 sys.exit(1) 1bfgadech

250 

251 

252class TyperArgument(click.core.Argument): 1bfgadech

253 def __init__( 1bfgadech

254 self, 

255 *, 

256 # Parameter 

257 param_decls: list[str], 

258 type: Optional[Any] = None, 

259 required: Optional[bool] = None, 

260 default: Optional[Any] = None, 

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

262 nargs: Optional[int] = None, 

263 metavar: Optional[str] = None, 

264 expose_value: bool = True, 

265 is_eager: bool = False, 

266 envvar: Optional[Union[str, list[str]]] = None, 

267 # Note that shell_complete is not fully supported and will be removed in future versions 

268 # TODO: Remove shell_complete in a future version (after 0.16.0) 

269 shell_complete: Optional[ 

270 Callable[ 

271 [click.Context, click.Parameter, str], 

272 Union[list["click.shell_completion.CompletionItem"], list[str]], 

273 ] 

274 ] = None, 

275 autocompletion: Optional[Callable[..., Any]] = None, 

276 # TyperArgument 

277 show_default: Union[bool, str] = True, 

278 show_choices: bool = True, 

279 show_envvar: bool = True, 

280 help: Optional[str] = None, 

281 hidden: bool = False, 

282 # Rich settings 

283 rich_help_panel: Union[str, None] = None, 

284 ): 

285 self.help = help 1bfgadech

286 self.show_default = show_default 1bfgadech

287 self.show_choices = show_choices 1bfgadech

288 self.show_envvar = show_envvar 1bfgadech

289 self.hidden = hidden 1bfgadech

290 self.rich_help_panel = rich_help_panel 1bfgadech

291 

292 super().__init__( 1bfgadech

293 param_decls=param_decls, 

294 type=type, 

295 required=required, 

296 default=default, 

297 callback=callback, 

298 nargs=nargs, 

299 metavar=metavar, 

300 expose_value=expose_value, 

301 is_eager=is_eager, 

302 envvar=envvar, 

303 shell_complete=shell_complete, 

304 ) 

305 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1bfgadech

306 

307 def _get_default_string( 1bfgadech

308 self, 

309 *, 

310 ctx: click.Context, 

311 show_default_is_str: bool, 

312 default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any], 

313 ) -> str: 

314 return _get_default_string( 1bfgadech

315 self, 

316 ctx=ctx, 

317 show_default_is_str=show_default_is_str, 

318 default_value=default_value, 

319 ) 

320 

321 def _extract_default_help_str( 1bfgadech

322 self, *, ctx: click.Context 

323 ) -> Optional[Union[Any, Callable[[], Any]]]: 

324 return _extract_default_help_str(self, ctx=ctx) 1bfgadech

325 

326 def get_help_record(self, ctx: click.Context) -> Optional[tuple[str, str]]: 1bfgadech

327 # Modified version of click.core.Option.get_help_record() 

328 # to support Arguments 

329 if self.hidden: 1bfgadech

330 return None 1bfgadech

331 name = self.make_metavar(ctx=ctx) 1bfgadech

332 help = self.help or "" 1bfgadech

333 extra = [] 1bfgadech

334 if self.show_envvar: 1bfgadech

335 envvar = self.envvar 1bfgadech

336 # allow_from_autoenv is currently not supported in Typer for CLI Arguments 

337 if envvar is not None: 1bfgadech

338 var_str = ( 1bfgadech

339 ", ".join(str(d) for d in envvar) 

340 if isinstance(envvar, (list, tuple)) 

341 else envvar 

342 ) 

343 extra.append(f"env var: {var_str}") 1bfgadech

344 

345 # Typer override: 

346 # Extracted to _extract_default_help_str() to allow re-using it in rich_utils 

347 default_value = self._extract_default_help_str(ctx=ctx) 1bfgadech

348 # Typer override end 

349 

350 show_default_is_str = isinstance(self.show_default, str) 1bfgadech

351 

352 if show_default_is_str or ( 1bfgadech

353 default_value is not None and (self.show_default or ctx.show_default) 

354 ): 

355 # Typer override: 

356 # Extracted to _get_default_string() to allow re-using it in rich_utils 

357 default_string = self._get_default_string( 1bfgadech

358 ctx=ctx, 

359 show_default_is_str=show_default_is_str, 

360 default_value=default_value, 

361 ) 

362 # Typer override end 

363 if default_string: 1bfgadech

364 extra.append(_("default: {default}").format(default=default_string)) 1bfgadech

365 if self.required: 1bfgadech

366 extra.append(_("required")) 1bfgadech

367 if extra: 1bfgadech

368 extra_str = "; ".join(extra) 1bfgadech

369 extra_str = f"[{extra_str}]" 1bfgadech

370 rich_markup_mode = None 1bfgadech

371 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1bfgadech

372 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1bfgadech

373 if HAS_RICH and rich_markup_mode == "rich": 1bfgadech

374 # This is needed for when we want to export to HTML 

375 from . import rich_utils 1bfgadech

376 

377 extra_str = rich_utils.escape_before_html_export(extra_str) 1bfgadech

378 

379 help = f"{help} {extra_str}" if help else f"{extra_str}" 1bfgadech

380 return name, help 1bfgadech

381 

382 def make_metavar(self, ctx: Union[click.Context, None] = None) -> str: 1bfgadech

383 # Modified version of click.core.Argument.make_metavar() 

384 # to include Argument name 

385 if self.metavar is not None: 1bfgadech

386 var = self.metavar 1bfgadech

387 if not self.required and not var.startswith("["): 1bfgadech

388 var = f"[{var}]" 1bfgadech

389 return var 1bfgadech

390 var = (self.name or "").upper() 1bfgadech

391 if not self.required: 1bfgadech

392 var = f"[{var}]" 1bfgadech

393 # TODO: When deprecating Click < 8.2, remove this 

394 signature = inspect.signature(self.type.get_metavar) 1bfgadech

395 if "ctx" in signature.parameters: 1bfgadech

396 # Click >= 8.2 

397 type_var = self.type.get_metavar(self, ctx=ctx) # type: ignore[arg-type] 1bfgdech

398 else: 

399 # Click < 8.2 

400 type_var = self.type.get_metavar(self) # type: ignore[call-arg] 1a

401 # TODO: /When deprecating Click < 8.2, remove this, uncomment the line below 

402 # type_var = self.type.get_metavar(self, ctx=ctx) 

403 if type_var: 1bfgadech

404 var += f":{type_var}" 1bfgadech

405 if self.nargs != 1: 1bfgadech

406 var += "..." 1bfgadech

407 return var 1bfgadech

408 

409 def value_is_missing(self, value: Any) -> bool: 1bfgadech

410 return _value_is_missing(self, value) 1bfgadech

411 

412 

413class TyperOption(click.core.Option): 1bfgadech

414 def __init__( 1bfgadech

415 self, 

416 *, 

417 # Parameter 

418 param_decls: list[str], 

419 type: Optional[Union[click.types.ParamType, Any]] = None, 

420 required: Optional[bool] = None, 

421 default: Optional[Any] = None, 

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

423 nargs: Optional[int] = None, 

424 metavar: Optional[str] = None, 

425 expose_value: bool = True, 

426 is_eager: bool = False, 

427 envvar: Optional[Union[str, list[str]]] = None, 

428 # Note that shell_complete is not fully supported and will be removed in future versions 

429 # TODO: Remove shell_complete in a future version (after 0.16.0) 

430 shell_complete: Optional[ 

431 Callable[ 

432 [click.Context, click.Parameter, str], 

433 Union[list["click.shell_completion.CompletionItem"], list[str]], 

434 ] 

435 ] = None, 

436 autocompletion: Optional[Callable[..., Any]] = None, 

437 # Option 

438 show_default: Union[bool, str] = False, 

439 prompt: Union[bool, str] = False, 

440 confirmation_prompt: Union[bool, str] = False, 

441 prompt_required: bool = True, 

442 hide_input: bool = False, 

443 is_flag: Optional[bool] = None, 

444 multiple: bool = False, 

445 count: bool = False, 

446 allow_from_autoenv: bool = True, 

447 help: Optional[str] = None, 

448 hidden: bool = False, 

449 show_choices: bool = True, 

450 show_envvar: bool = False, 

451 # Rich settings 

452 rich_help_panel: Union[str, None] = None, 

453 ): 

454 super().__init__( 1bfgadech

455 param_decls=param_decls, 

456 type=type, 

457 required=required, 

458 default=default, 

459 callback=callback, 

460 nargs=nargs, 

461 metavar=metavar, 

462 expose_value=expose_value, 

463 is_eager=is_eager, 

464 envvar=envvar, 

465 show_default=show_default, 

466 prompt=prompt, 

467 confirmation_prompt=confirmation_prompt, 

468 hide_input=hide_input, 

469 is_flag=is_flag, 

470 multiple=multiple, 

471 count=count, 

472 allow_from_autoenv=allow_from_autoenv, 

473 help=help, 

474 hidden=hidden, 

475 show_choices=show_choices, 

476 show_envvar=show_envvar, 

477 prompt_required=prompt_required, 

478 shell_complete=shell_complete, 

479 ) 

480 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1bfgadech

481 self.rich_help_panel = rich_help_panel 1bfgadech

482 

483 def _get_default_string( 1bfgadech

484 self, 

485 *, 

486 ctx: click.Context, 

487 show_default_is_str: bool, 

488 default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any], 

489 ) -> str: 

490 return _get_default_string( 1bfgadech

491 self, 

492 ctx=ctx, 

493 show_default_is_str=show_default_is_str, 

494 default_value=default_value, 

495 ) 

496 

497 def _extract_default_help_str( 1bfgadech

498 self, *, ctx: click.Context 

499 ) -> Optional[Union[Any, Callable[[], Any]]]: 

500 return _extract_default_help_str(self, ctx=ctx) 1bfgadech

501 

502 def make_metavar(self, ctx: Union[click.Context, None] = None) -> str: 1bfgadech

503 signature = inspect.signature(super().make_metavar) 1bfgadech

504 if "ctx" in signature.parameters: 1bfgadech

505 # Click >= 8.2 

506 return super().make_metavar(ctx=ctx) # type: ignore[arg-type] 1bfgdech

507 # Click < 8.2 

508 return super().make_metavar() # type: ignore[call-arg] 1a

509 

510 def get_help_record(self, ctx: click.Context) -> Optional[tuple[str, str]]: 1bfgadech

511 # Duplicate all of Click's logic only to modify a single line, to allow boolean 

512 # flags with only names for False values as it's currently supported by Typer 

513 # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false 

514 if self.hidden: 1bfgadech

515 return None 1bfgadech

516 

517 any_prefix_is_slash = False 1bfgadech

518 

519 def _write_opts(opts: Sequence[str]) -> str: 1bfgadech

520 nonlocal any_prefix_is_slash 

521 

522 rv, any_slashes = click.formatting.join_options(opts) 1bfgadech

523 

524 if any_slashes: 1bfgadech

525 any_prefix_is_slash = True 1bfgadech

526 

527 if not self.is_flag and not self.count: 1bfgadech

528 rv += f" {self.make_metavar(ctx=ctx)}" 1bfgadech

529 

530 return rv 1bfgadech

531 

532 rv = [_write_opts(self.opts)] 1bfgadech

533 

534 if self.secondary_opts: 1bfgadech

535 rv.append(_write_opts(self.secondary_opts)) 1bfgadech

536 

537 help = self.help or "" 1bfgadech

538 extra = [] 1bfgadech

539 

540 if self.show_envvar: 1bfgadech

541 envvar = self.envvar 1bfgadech

542 

543 if envvar is None: 1bfgadech

544 if ( 1bac

545 self.allow_from_autoenv 

546 and ctx.auto_envvar_prefix is not None 

547 and self.name is not None 

548 ): 

549 envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" 1bfgadech

550 

551 if envvar is not None: 1bfgadech

552 var_str = ( 1bfgadech

553 envvar 

554 if isinstance(envvar, str) 

555 else ", ".join(str(d) for d in envvar) 

556 ) 

557 extra.append(_("env var: {var}").format(var=var_str)) 1bfgadech

558 

559 # Typer override: 

560 # Extracted to _extract_default() to allow re-using it in rich_utils 

561 default_value = self._extract_default_help_str(ctx=ctx) 1bfgadech

562 # Typer override end 

563 

564 show_default_is_str = isinstance(self.show_default, str) 1bfgadech

565 

566 if show_default_is_str or ( 1bfgadech

567 default_value is not None and (self.show_default or ctx.show_default) 

568 ): 

569 # Typer override: 

570 # Extracted to _get_default_string() to allow re-using it in rich_utils 

571 default_string = self._get_default_string( 1bfgadech

572 ctx=ctx, 

573 show_default_is_str=show_default_is_str, 

574 default_value=default_value, 

575 ) 

576 # Typer override end 

577 if default_string: 1bfgadech

578 extra.append(_("default: {default}").format(default=default_string)) 1bfgadech

579 

580 if isinstance(self.type, click.types._NumberRangeBase): 1bfgadech

581 range_str = self.type._describe_range() 1bfgadech

582 

583 if range_str: 1bfgadech

584 extra.append(range_str) 1bfgadech

585 

586 if self.required: 1bfgadech

587 extra.append(_("required")) 1bfgadech

588 

589 if extra: 1bfgadech

590 extra_str = "; ".join(extra) 1bfgadech

591 extra_str = f"[{extra_str}]" 1bfgadech

592 rich_markup_mode = None 1bfgadech

593 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1bfgadech

594 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1bfgadech

595 if HAS_RICH and rich_markup_mode == "rich": 1bfgadech

596 # This is needed for when we want to export to HTML 

597 from . import rich_utils 1bfgadech

598 

599 extra_str = rich_utils.escape_before_html_export(extra_str) 1bfgadech

600 

601 help = f"{help} {extra_str}" if help else f"{extra_str}" 1bfgadech

602 

603 return ("; " if any_prefix_is_slash else " / ").join(rv), help 1bfgadech

604 

605 def value_is_missing(self, value: Any) -> bool: 1bfgadech

606 return _value_is_missing(self, value) 1bfgadech

607 

608 

609def _value_is_missing(param: click.Parameter, value: Any) -> bool: 1bfgadech

610 if value is None: 1bfgadech

611 return True 1bfgadech

612 

613 # Click 8.3 and beyond 

614 # if value is UNSET: 

615 # return True 

616 

617 if (param.nargs != 1 or param.multiple) and value == (): 1bfgadech

618 return True # pragma: no cover 

619 

620 return False 1bfgadech

621 

622 

623def _typer_format_options( 1bfgadech

624 self: click.core.Command, *, ctx: click.Context, formatter: click.HelpFormatter 

625) -> None: 

626 args = [] 1bfgadech

627 opts = [] 1bfgadech

628 for param in self.get_params(ctx): 1bfgadech

629 rv = param.get_help_record(ctx) 1bfgadech

630 if rv is not None: 1bfgadech

631 if param.param_type_name == "argument": 1bfgadech

632 args.append(rv) 1bfgadech

633 elif param.param_type_name == "option": 1bfgadech

634 opts.append(rv) 1bfgadech

635 

636 if args: 1bfgadech

637 with formatter.section(_("Arguments")): 1bfgadech

638 formatter.write_dl(args) 1bfgadech

639 if opts: 1bfgadech

640 with formatter.section(_("Options")): 1bfgadech

641 formatter.write_dl(opts) 1bfgadech

642 

643 

644def _typer_main_shell_completion( 1bfgadech

645 self: click.core.Command, 

646 *, 

647 ctx_args: MutableMapping[str, Any], 

648 prog_name: str, 

649 complete_var: Optional[str] = None, 

650) -> None: 

651 if complete_var is None: 1bfgadech

652 complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() 1bfgadech

653 

654 instruction = os.environ.get(complete_var) 1bfgadech

655 

656 if not instruction: 1bfgadech

657 return 1bfgadech

658 

659 from .completion import shell_complete 1bfgadech

660 

661 rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) 1bfgadech

662 sys.exit(rv) 1bfgadech

663 

664 

665class TyperCommand(click.core.Command): 1bfgadech

666 def __init__( 1bfgadech

667 self, 

668 name: Optional[str], 

669 *, 

670 context_settings: Optional[dict[str, Any]] = None, 

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

672 params: Optional[list[click.Parameter]] = None, 

673 help: Optional[str] = None, 

674 epilog: Optional[str] = None, 

675 short_help: Optional[str] = None, 

676 options_metavar: Optional[str] = "[OPTIONS]", 

677 add_help_option: bool = True, 

678 no_args_is_help: bool = False, 

679 hidden: bool = False, 

680 deprecated: bool = False, 

681 # Rich settings 

682 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

683 rich_help_panel: Union[str, None] = None, 

684 ) -> None: 

685 super().__init__( 1bfgadech

686 name=name, 

687 context_settings=context_settings, 

688 callback=callback, 

689 params=params, 

690 help=help, 

691 epilog=epilog, 

692 short_help=short_help, 

693 options_metavar=options_metavar, 

694 add_help_option=add_help_option, 

695 no_args_is_help=no_args_is_help, 

696 hidden=hidden, 

697 deprecated=deprecated, 

698 ) 

699 self.rich_markup_mode: MarkupMode = rich_markup_mode 1bfgadech

700 self.rich_help_panel = rich_help_panel 1bfgadech

701 

702 def format_options( 1bfgadech

703 self, ctx: click.Context, formatter: click.HelpFormatter 

704 ) -> None: 

705 _typer_format_options(self, ctx=ctx, formatter=formatter) 1bfgadech

706 

707 def _main_shell_completion( 1bfgadech

708 self, 

709 ctx_args: MutableMapping[str, Any], 

710 prog_name: str, 

711 complete_var: Optional[str] = None, 

712 ) -> None: 

713 _typer_main_shell_completion( 1bfgadech

714 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

715 ) 

716 

717 def main( 1bfgadech

718 self, 

719 args: Optional[Sequence[str]] = None, 

720 prog_name: Optional[str] = None, 

721 complete_var: Optional[str] = None, 

722 standalone_mode: bool = True, 

723 windows_expand_args: bool = True, 

724 **extra: Any, 

725 ) -> Any: 

726 return _main( 1bfgadech

727 self, 

728 args=args, 

729 prog_name=prog_name, 

730 complete_var=complete_var, 

731 standalone_mode=standalone_mode, 

732 windows_expand_args=windows_expand_args, 

733 rich_markup_mode=self.rich_markup_mode, 

734 **extra, 

735 ) 

736 

737 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1bfgadech

738 if not HAS_RICH or self.rich_markup_mode is None: 1bfgadech

739 if not hasattr(ctx, "obj") or ctx.obj is None: 1bfgadech

740 ctx.ensure_object(dict) 1bfgadech

741 if isinstance(ctx.obj, dict): 1bfgadech

742 ctx.obj[MARKUP_MODE_KEY] = self.rich_markup_mode 1bfgadech

743 return super().format_help(ctx, formatter) 1bfgadech

744 from . import rich_utils 1bfgadech

745 

746 return rich_utils.rich_format_help( 1bfgadech

747 obj=self, 

748 ctx=ctx, 

749 markup_mode=self.rich_markup_mode, 

750 ) 

751 

752 

753class TyperGroup(click.core.Group): 1bfgadech

754 def __init__( 1bfgadech

755 self, 

756 *, 

757 name: Optional[str] = None, 

758 commands: Optional[ 

759 Union[dict[str, click.Command], Sequence[click.Command]] 

760 ] = None, 

761 # Rich settings 

762 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

763 rich_help_panel: Union[str, None] = None, 

764 suggest_commands: bool = True, 

765 **attrs: Any, 

766 ) -> None: 

767 super().__init__(name=name, commands=commands, **attrs) 1bfgadech

768 self.rich_markup_mode: MarkupMode = rich_markup_mode 1bfgadech

769 self.rich_help_panel = rich_help_panel 1bfgadech

770 self.suggest_commands = suggest_commands 1bfgadech

771 

772 def format_options( 1bfgadech

773 self, ctx: click.Context, formatter: click.HelpFormatter 

774 ) -> None: 

775 _typer_format_options(self, ctx=ctx, formatter=formatter) 1bfgadech

776 self.format_commands(ctx, formatter) 1bfgadech

777 

778 def _main_shell_completion( 1bfgadech

779 self, 

780 ctx_args: MutableMapping[str, Any], 

781 prog_name: str, 

782 complete_var: Optional[str] = None, 

783 ) -> None: 

784 _typer_main_shell_completion( 1bfgadech

785 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

786 ) 

787 

788 def resolve_command( 1bfgadech

789 self, ctx: click.Context, args: list[str] 

790 ) -> tuple[Optional[str], Optional[click.Command], list[str]]: 

791 try: 1bfgadech

792 return super().resolve_command(ctx, args) 1bfgadech

793 except click.UsageError as e: 1bfgadech

794 if self.suggest_commands: 1bfgadech

795 available_commands = list(self.commands.keys()) 1bfgadech

796 if available_commands and args: 1bfgadech

797 typo = args[0] 1bfgadech

798 matches = get_close_matches(typo, available_commands) 1bfgadech

799 if matches: 1bfgadech

800 suggestions = ", ".join(f"{m!r}" for m in matches) 1bfgadech

801 message = e.message.rstrip(".") 1bfgadech

802 e.message = f"{message}. Did you mean {suggestions}?" 1bfgadech

803 raise 1bfgadech

804 

805 def main( 1bfgadech

806 self, 

807 args: Optional[Sequence[str]] = None, 

808 prog_name: Optional[str] = None, 

809 complete_var: Optional[str] = None, 

810 standalone_mode: bool = True, 

811 windows_expand_args: bool = True, 

812 **extra: Any, 

813 ) -> Any: 

814 return _main( 1bfgadech

815 self, 

816 args=args, 

817 prog_name=prog_name, 

818 complete_var=complete_var, 

819 standalone_mode=standalone_mode, 

820 windows_expand_args=windows_expand_args, 

821 rich_markup_mode=self.rich_markup_mode, 

822 **extra, 

823 ) 

824 

825 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1bfgadech

826 if not HAS_RICH or self.rich_markup_mode is None: 1bfgadech

827 return super().format_help(ctx, formatter) 1bfgadech

828 from . import rich_utils 1bfgadech

829 

830 return rich_utils.rich_format_help( 1bfgadech

831 obj=self, 

832 ctx=ctx, 

833 markup_mode=self.rich_markup_mode, 

834 ) 

835 

836 def list_commands(self, ctx: click.Context) -> list[str]: 1bfgadech

837 """Returns a list of subcommand names. 

838 Note that in Click's Group class, these are sorted. 

839 In Typer, we wish to maintain the original order of creation (cf Issue #933)""" 

840 return [n for n, c in self.commands.items()] 1bfgadech