Coverage for typer/core.py: 100%

317 statements  

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

1import errno 1bghaefcdi

2import importlib.util 1bghaefcdi

3import inspect 1bghaefcdi

4import os 1bghaefcdi

5import sys 1bghaefcdi

6from difflib import get_close_matches 1bghaefcdi

7from enum import Enum 1bghaefcdi

8from gettext import gettext as _ 1bghaefcdi

9from typing import ( 1bghaefcdi

10 Any, 

11 Callable, 

12 Dict, 

13 List, 

14 MutableMapping, 

15 Optional, 

16 Sequence, 

17 TextIO, 

18 Tuple, 

19 Union, 

20 cast, 

21) 

22 

23import click 1bghaefcdi

24import click.core 1bghaefcdi

25import click.formatting 1bghaefcdi

26import click.shell_completion 1bghaefcdi

27import click.types 1bghaefcdi

28import click.utils 1bghaefcdi

29 

30from ._typing import Literal 1bghaefcdi

31 

32MarkupMode = Literal["markdown", "rich", None] 1bghaefcdi

33 

34HAS_RICH = importlib.util.find_spec("rich") is not None 1bghaefcdi

35HAS_SHELLINGHAM = importlib.util.find_spec("shellingham") is not None 1bghaefcdi

36 

37if HAS_RICH: 1bghaefcdi

38 DEFAULT_MARKUP_MODE: MarkupMode = "rich" 1bghaefcdi

39else: # pragma: no cover 

40 DEFAULT_MARKUP_MODE = None 

41 

42 

43# Copy from click.parser._split_opt 

44def _split_opt(opt: str) -> Tuple[str, str]: 1bghaefcdi

45 first = opt[:1] 1bghaefcdi

46 if first.isalnum(): 1bghaefcdi

47 return "", opt 1bghaefcdi

48 if opt[1:2] == first: 1bghaefcdi

49 return opt[:2], opt[2:] 1bghaefcdi

50 return first, opt[1:] 1bghaefcdi

51 

52 

53def _typer_param_setup_autocompletion_compat( 1bghaefcdi

54 self: click.Parameter, 

55 *, 

56 autocompletion: Optional[ 

57 Callable[[click.Context, List[str], str], List[Union[Tuple[str, str], str]]] 

58 ] = None, 

59) -> None: 

60 if self._custom_shell_complete is not None: 1bghaefcdi

61 import warnings 1bghaefcdi

62 

63 warnings.warn( 1bghaefcdi

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

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

66 DeprecationWarning, 

67 stacklevel=2, 

68 ) 

69 

70 if autocompletion is not None: 1bghaefcdi

71 

72 def compat_autocompletion( 1bghaefcdi

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

74 ) -> List["click.shell_completion.CompletionItem"]: 

75 from click.shell_completion import CompletionItem 1bghaefcdi

76 

77 out = [] 1bghaefcdi

78 

79 for c in autocompletion(ctx, [], incomplete): 1bghaefcdi

80 if isinstance(c, tuple): 1bghaefcdi

81 use_completion = CompletionItem(c[0], help=c[1]) 1bghaefcdi

82 else: 

83 assert isinstance(c, str) 1bghaefcdi

84 use_completion = CompletionItem(c) 1bghaefcdi

85 

86 if use_completion.value.startswith(incomplete): 1bghaefcdi

87 out.append(use_completion) 1bghaefcdi

88 

89 return out 1bghaefcdi

90 

91 self._custom_shell_complete = compat_autocompletion 1bghaefcdi

92 

93 

94def _get_default_string( 1bghaefcdi

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

96 *, 

97 ctx: click.Context, 

98 show_default_is_str: bool, 

99 default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], 

100) -> str: 

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

102 # rich_utils avoiding RegEx hacks 

103 if show_default_is_str: 1bghaefcdi

104 default_string = f"({obj.show_default})" 1bghaefcdi

105 elif isinstance(default_value, (list, tuple)): 1bghaefcdi

106 default_string = ", ".join( 1bghaefcdi

107 _get_default_string( 

108 obj, ctx=ctx, show_default_is_str=show_default_is_str, default_value=d 

109 ) 

110 for d in default_value 

111 ) 

112 elif isinstance(default_value, Enum): 1bghaefcdi

113 default_string = str(default_value.value) 1bghaefcdi

114 elif inspect.isfunction(default_value): 1bghaefcdi

115 default_string = _("(dynamic)") 1bghaefcdi

116 elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: 1bghaefcdi

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

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

119 # Typer override, original commented 

120 # default_string = click.parser.split_opt( 

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

122 # )[1] 

123 if obj.default: 1bghaefcdi

124 if obj.opts: 1bghaefcdi

125 default_string = _split_opt(obj.opts[0])[1] 1bghaefcdi

126 else: 

127 default_string = str(default_value) 1bghaefcdi

128 else: 

129 default_string = _split_opt(obj.secondary_opts[0])[1] 1bghaefcdi

130 # Typer override end 

131 elif ( 1bacd

132 isinstance(obj, TyperOption) 

133 and obj.is_bool_flag 

134 and not obj.secondary_opts 

135 and not default_value 

136 ): 

137 default_string = "" 1bghaefcdi

138 else: 

139 default_string = str(default_value) 1bghaefcdi

140 return default_string 1bghaefcdi

141 

142 

143def _extract_default_help_str( 1bghaefcdi

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

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

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

147 # rich_utils avoiding RegEx hacks 

148 # Temporarily enable resilient parsing to avoid type casting 

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

150 # help formatting in general. 

151 resilient = ctx.resilient_parsing 1bghaefcdi

152 ctx.resilient_parsing = True 1bghaefcdi

153 

154 try: 1bghaefcdi

155 default_value = obj.get_default(ctx, call=False) 1bghaefcdi

156 finally: 

157 ctx.resilient_parsing = resilient 1bghaefcdi

158 return default_value 1bghaefcdi

159 

160 

161def _main( 1bghaefcdi

162 self: click.Command, 

163 *, 

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

165 prog_name: Optional[str] = None, 

166 complete_var: Optional[str] = None, 

167 standalone_mode: bool = True, 

168 windows_expand_args: bool = True, 

169 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

170 **extra: Any, 

171) -> Any: 

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

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

174 # further execution to avoid a broken script. 

175 if args is None: 1bghaefcdi

176 args = sys.argv[1:] 1bghaefcdi

177 

178 # Covered in Click tests 

179 if os.name == "nt" and windows_expand_args: # pragma: no cover 1bghaefcdi

180 args = click.utils._expand_args(args) 1aef

181 else: 

182 args = list(args) 1bghaefcdi

183 

184 if prog_name is None: 1bghaefcdi

185 prog_name = click.utils._detect_program_name() 1bghaefcdi

186 

187 # Process shell completion requests and exit early. 

188 self._main_shell_completion(extra, prog_name, complete_var) 1bghaefcdi

189 

190 try: 1bghaefcdi

191 try: 1bghaefcdi

192 with self.make_context(prog_name, args, **extra) as ctx: 1bghaefcdi

193 rv = self.invoke(ctx) 1bghaefcdi

194 if not standalone_mode: 1bghaefcdi

195 return rv 1bghaefcdi

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

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

198 # has obvious effects 

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

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

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

202 # by its truthiness/falsiness 

203 ctx.exit() 1bghaefcdi

204 except EOFError as e: 1bghaefcdi

205 click.echo(file=sys.stderr) 1bghaefcdi

206 raise click.Abort() from e 1bghaefcdi

207 except KeyboardInterrupt as e: 1bghaefcdi

208 raise click.exceptions.Exit(130) from e 1bghaefcdi

209 except click.ClickException as e: 1bghaefcdi

210 if not standalone_mode: 1bghaefcdi

211 raise 1bghaefcdi

212 # Typer override 

213 if HAS_RICH and rich_markup_mode is not None: 1bghaefcdi

214 from . import rich_utils 1bghaefcdi

215 

216 rich_utils.rich_format_error(e) 1bghaefcdi

217 else: 

218 e.show() 1bghaefcdi

219 # Typer override end 

220 sys.exit(e.exit_code) 1bghaefcdi

221 except OSError as e: 1bghaefcdi

222 if e.errno == errno.EPIPE: 1bghaefcdi

223 sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout)) 1bghaefcdi

224 sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr)) 1bghaefcdi

225 sys.exit(1) 1bghaefcdi

226 else: 

227 raise 1bghaefcdi

228 except click.exceptions.Exit as e: 1bghaefcdi

229 if standalone_mode: 1bghaefcdi

230 sys.exit(e.exit_code) 1bghaefcdi

231 else: 

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

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

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

235 # would return its result 

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

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

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

239 # tell the difference between the two 

240 return e.exit_code 1bghaefcdi

241 except click.Abort: 1bghaefcdi

242 if not standalone_mode: 1bghaefcdi

243 raise 1bghaefcdi

244 # Typer override 

245 if HAS_RICH and rich_markup_mode is not None: 1bghaefcdi

246 from . import rich_utils 1bghaefcdi

247 

248 rich_utils.rich_abort_error() 1bghaefcdi

249 else: 

250 click.echo(_("Aborted!"), file=sys.stderr) 1bghaefcdi

251 # Typer override end 

252 sys.exit(1) 1bghaefcdi

253 

254 

255class TyperArgument(click.core.Argument): 1bghaefcdi

256 def __init__( 1bghaefcdi

257 self, 

258 *, 

259 # Parameter 

260 param_decls: List[str], 

261 type: Optional[Any] = None, 

262 required: Optional[bool] = None, 

263 default: Optional[Any] = None, 

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

265 nargs: Optional[int] = None, 

266 metavar: Optional[str] = None, 

267 expose_value: bool = True, 

268 is_eager: bool = False, 

269 envvar: Optional[Union[str, List[str]]] = None, 

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

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

272 shell_complete: Optional[ 

273 Callable[ 

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

275 Union[List["click.shell_completion.CompletionItem"], List[str]], 

276 ] 

277 ] = None, 

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

279 # TyperArgument 

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

281 show_choices: bool = True, 

282 show_envvar: bool = True, 

283 help: Optional[str] = None, 

284 hidden: bool = False, 

285 # Rich settings 

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

287 ): 

288 self.help = help 1bghaefcdi

289 self.show_default = show_default 1bghaefcdi

290 self.show_choices = show_choices 1bghaefcdi

291 self.show_envvar = show_envvar 1bghaefcdi

292 self.hidden = hidden 1bghaefcdi

293 self.rich_help_panel = rich_help_panel 1bghaefcdi

294 

295 super().__init__( 1bghaefcdi

296 param_decls=param_decls, 

297 type=type, 

298 required=required, 

299 default=default, 

300 callback=callback, 

301 nargs=nargs, 

302 metavar=metavar, 

303 expose_value=expose_value, 

304 is_eager=is_eager, 

305 envvar=envvar, 

306 shell_complete=shell_complete, 

307 ) 

308 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1bghaefcdi

309 

310 def _get_default_string( 1bghaefcdi

311 self, 

312 *, 

313 ctx: click.Context, 

314 show_default_is_str: bool, 

315 default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], 

316 ) -> str: 

317 return _get_default_string( 1bghaefcdi

318 self, 

319 ctx=ctx, 

320 show_default_is_str=show_default_is_str, 

321 default_value=default_value, 

322 ) 

323 

324 def _extract_default_help_str( 1bghaefcdi

325 self, *, ctx: click.Context 

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

327 return _extract_default_help_str(self, ctx=ctx) 1bghaefcdi

328 

329 def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: 1bghaefcdi

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

331 # to support Arguments 

332 if self.hidden: 1bghaefcdi

333 return None 1bghaefcdi

334 name = self.make_metavar(ctx=ctx) 1bghaefcdi

335 help = self.help or "" 1bghaefcdi

336 extra = [] 1bghaefcdi

337 if self.show_envvar: 1bghaefcdi

338 envvar = self.envvar 1bghaefcdi

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

340 if envvar is not None: 1bghaefcdi

341 var_str = ( 1bghaefcdi

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

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

344 else envvar 

345 ) 

346 extra.append(f"env var: {var_str}") 1bghaefcdi

347 

348 # Typer override: 

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

350 default_value = self._extract_default_help_str(ctx=ctx) 1bghaefcdi

351 # Typer override end 

352 

353 show_default_is_str = isinstance(self.show_default, str) 1bghaefcdi

354 

355 if show_default_is_str or ( 1bghaefcdi

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

357 ): 

358 # Typer override: 

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

360 default_string = self._get_default_string( 1bghaefcdi

361 ctx=ctx, 

362 show_default_is_str=show_default_is_str, 

363 default_value=default_value, 

364 ) 

365 # Typer override end 

366 if default_string: 1bghaefcdi

367 extra.append(_("default: {default}").format(default=default_string)) 1bghaefcdi

368 if self.required: 1bghaefcdi

369 extra.append(_("required")) 1bghaefcdi

370 if extra: 1bghaefcdi

371 extra_str = "; ".join(extra) 1bghaefcdi

372 extra_str = f"[{extra_str}]" 1bghaefcdi

373 if HAS_RICH: 1bghaefcdi

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

375 from . import rich_utils 1bghaefcdi

376 

377 extra_str = rich_utils.escape_before_html_export(extra_str) 1bghaefcdi

378 

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

380 return name, help 1bghaefcdi

381 

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

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

384 # to include Argument name 

385 if self.metavar is not None: 1bghaefcdi

386 var = self.metavar 1bghaefcdi

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

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

389 return var 1bghaefcdi

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

391 if not self.required: 1bghaefcdi

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

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

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

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

396 # Click >= 8.2 

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

398 else: 

399 # Click < 8.2 

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

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

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

405 if self.nargs != 1: 1bghaefcdi

406 var += "..." 1bghaefcdi

407 return var 1bghaefcdi

408 

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

410 return _value_is_missing(self, value) 1bghaefcdi

411 

412 

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

414 def __init__( 1bghaefcdi

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__( 1bghaefcdi

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

481 self.rich_help_panel = rich_help_panel 1bghaefcdi

482 

483 def _get_default_string( 1bghaefcdi

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

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

498 self, *, ctx: click.Context 

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

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

501 

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

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

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

505 # Click >= 8.2 

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

507 # Click < 8.2 

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

509 

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

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

515 return None 1bghaefcdi

516 

517 any_prefix_is_slash = False 1bghaefcdi

518 

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

520 nonlocal any_prefix_is_slash 

521 

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

523 

524 if any_slashes: 1bghaefcdi

525 any_prefix_is_slash = True 1bghaefcdi

526 

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

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

529 

530 return rv 1bghaefcdi

531 

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

533 

534 if self.secondary_opts: 1bghaefcdi

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

536 

537 help = self.help or "" 1bghaefcdi

538 extra = [] 1bghaefcdi

539 

540 if self.show_envvar: 1bghaefcdi

541 envvar = self.envvar 1bghaefcdi

542 

543 if envvar is None: 1bghaefcdi

544 if ( 1bacd

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()}" 1bghaefcdi

550 

551 if envvar is not None: 1bghaefcdi

552 var_str = ( 1bghaefcdi

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

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

562 # Typer override end 

563 

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

565 

566 if show_default_is_str or ( 1bghaefcdi

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

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

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

579 

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

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

582 

583 if range_str: 1bghaefcdi

584 extra.append(range_str) 1bghaefcdi

585 

586 if self.required: 1bghaefcdi

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

588 

589 if extra: 1bghaefcdi

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

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

592 if HAS_RICH: 1bghaefcdi

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

594 from . import rich_utils 1bghaefcdi

595 

596 extra_str = rich_utils.escape_before_html_export(extra_str) 1bghaefcdi

597 

598 help = f"{help} {extra_str}" if help else f"{extra_str}" 1bghaefcdi

599 

600 return ("; " if any_prefix_is_slash else " / ").join(rv), help 1bghaefcdi

601 

602 def value_is_missing(self, value: Any) -> bool: 1bghaefcdi

603 return _value_is_missing(self, value) 1bghaefcdi

604 

605 

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

607 if value is None: 1bghaefcdi

608 return True 1bghaefcdi

609 

610 # Click 8.3 and beyond 

611 # if value is UNSET: 

612 # return True 

613 

614 if (param.nargs != 1 or param.multiple) and value == (): 1bghaefcdi

615 return True # pragma: no cover 

616 

617 return False 1bghaefcdi

618 

619 

620def _typer_format_options( 1bghaefcdi

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

622) -> None: 

623 args = [] 1bghaefcdi

624 opts = [] 1bghaefcdi

625 for param in self.get_params(ctx): 1bghaefcdi

626 rv = param.get_help_record(ctx) 1bghaefcdi

627 if rv is not None: 1bghaefcdi

628 if param.param_type_name == "argument": 1bghaefcdi

629 args.append(rv) 1bghaefcdi

630 elif param.param_type_name == "option": 1bghaefcdi

631 opts.append(rv) 1bghaefcdi

632 

633 if args: 1bghaefcdi

634 with formatter.section(_("Arguments")): 1bghaefcdi

635 formatter.write_dl(args) 1bghaefcdi

636 if opts: 1bghaefcdi

637 with formatter.section(_("Options")): 1bghaefcdi

638 formatter.write_dl(opts) 1bghaefcdi

639 

640 

641def _typer_main_shell_completion( 1bghaefcdi

642 self: click.core.Command, 

643 *, 

644 ctx_args: MutableMapping[str, Any], 

645 prog_name: str, 

646 complete_var: Optional[str] = None, 

647) -> None: 

648 if complete_var is None: 1bghaefcdi

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

650 

651 instruction = os.environ.get(complete_var) 1bghaefcdi

652 

653 if not instruction: 1bghaefcdi

654 return 1bghaefcdi

655 

656 from .completion import shell_complete 1bghaefcdi

657 

658 rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) 1bghaefcdi

659 sys.exit(rv) 1bghaefcdi

660 

661 

662class TyperCommand(click.core.Command): 1bghaefcdi

663 def __init__( 1bghaefcdi

664 self, 

665 name: Optional[str], 

666 *, 

667 context_settings: Optional[Dict[str, Any]] = None, 

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

669 params: Optional[List[click.Parameter]] = None, 

670 help: Optional[str] = None, 

671 epilog: Optional[str] = None, 

672 short_help: Optional[str] = None, 

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

674 add_help_option: bool = True, 

675 no_args_is_help: bool = False, 

676 hidden: bool = False, 

677 deprecated: bool = False, 

678 # Rich settings 

679 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

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

681 ) -> None: 

682 super().__init__( 1bghaefcdi

683 name=name, 

684 context_settings=context_settings, 

685 callback=callback, 

686 params=params, 

687 help=help, 

688 epilog=epilog, 

689 short_help=short_help, 

690 options_metavar=options_metavar, 

691 add_help_option=add_help_option, 

692 no_args_is_help=no_args_is_help, 

693 hidden=hidden, 

694 deprecated=deprecated, 

695 ) 

696 self.rich_markup_mode: MarkupMode = rich_markup_mode 1bghaefcdi

697 self.rich_help_panel = rich_help_panel 1bghaefcdi

698 

699 def format_options( 1bghaefcdi

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

701 ) -> None: 

702 _typer_format_options(self, ctx=ctx, formatter=formatter) 1bghaefcdi

703 

704 def _main_shell_completion( 1bghaefcdi

705 self, 

706 ctx_args: MutableMapping[str, Any], 

707 prog_name: str, 

708 complete_var: Optional[str] = None, 

709 ) -> None: 

710 _typer_main_shell_completion( 1bghaefcdi

711 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

712 ) 

713 

714 def main( 1bghaefcdi

715 self, 

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

717 prog_name: Optional[str] = None, 

718 complete_var: Optional[str] = None, 

719 standalone_mode: bool = True, 

720 windows_expand_args: bool = True, 

721 **extra: Any, 

722 ) -> Any: 

723 return _main( 1bghaefcdi

724 self, 

725 args=args, 

726 prog_name=prog_name, 

727 complete_var=complete_var, 

728 standalone_mode=standalone_mode, 

729 windows_expand_args=windows_expand_args, 

730 rich_markup_mode=self.rich_markup_mode, 

731 **extra, 

732 ) 

733 

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

735 if not HAS_RICH or self.rich_markup_mode is None: 1bghaefcdi

736 return super().format_help(ctx, formatter) 1bghaefcdi

737 from . import rich_utils 1bghaefcdi

738 

739 return rich_utils.rich_format_help( 1bghaefcdi

740 obj=self, 

741 ctx=ctx, 

742 markup_mode=self.rich_markup_mode, 

743 ) 

744 

745 

746class TyperGroup(click.core.Group): 1bghaefcdi

747 def __init__( 1bghaefcdi

748 self, 

749 *, 

750 name: Optional[str] = None, 

751 commands: Optional[ 

752 Union[Dict[str, click.Command], Sequence[click.Command]] 

753 ] = None, 

754 # Rich settings 

755 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

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

757 suggest_commands: bool = True, 

758 **attrs: Any, 

759 ) -> None: 

760 super().__init__(name=name, commands=commands, **attrs) 1bghaefcdi

761 self.rich_markup_mode: MarkupMode = rich_markup_mode 1bghaefcdi

762 self.rich_help_panel = rich_help_panel 1bghaefcdi

763 self.suggest_commands = suggest_commands 1bghaefcdi

764 

765 def format_options( 1bghaefcdi

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

767 ) -> None: 

768 _typer_format_options(self, ctx=ctx, formatter=formatter) 1bghaefcdi

769 self.format_commands(ctx, formatter) 1bghaefcdi

770 

771 def _main_shell_completion( 1bghaefcdi

772 self, 

773 ctx_args: MutableMapping[str, Any], 

774 prog_name: str, 

775 complete_var: Optional[str] = None, 

776 ) -> None: 

777 _typer_main_shell_completion( 1bghaefcdi

778 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

779 ) 

780 

781 def resolve_command( 1bghaefcdi

782 self, ctx: click.Context, args: List[str] 

783 ) -> Tuple[Optional[str], Optional[click.Command], List[str]]: 

784 try: 1bghaefcdi

785 return super().resolve_command(ctx, args) 1bghaefcdi

786 except click.UsageError as e: 1bghaefcdi

787 if self.suggest_commands: 1bghaefcdi

788 available_commands = list(self.commands.keys()) 1bghaefcdi

789 if available_commands and args: 1bghaefcdi

790 typo = args[0] 1bghaefcdi

791 matches = get_close_matches(typo, available_commands) 1bghaefcdi

792 if matches: 1bghaefcdi

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

794 message = e.message.rstrip(".") 1bghaefcdi

795 e.message = f"{message}. Did you mean {suggestions}?" 1bghaefcdi

796 raise 1bghaefcdi

797 

798 def main( 1bghaefcdi

799 self, 

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

801 prog_name: Optional[str] = None, 

802 complete_var: Optional[str] = None, 

803 standalone_mode: bool = True, 

804 windows_expand_args: bool = True, 

805 **extra: Any, 

806 ) -> Any: 

807 return _main( 1bghaefcdi

808 self, 

809 args=args, 

810 prog_name=prog_name, 

811 complete_var=complete_var, 

812 standalone_mode=standalone_mode, 

813 windows_expand_args=windows_expand_args, 

814 rich_markup_mode=self.rich_markup_mode, 

815 **extra, 

816 ) 

817 

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

819 if not HAS_RICH or self.rich_markup_mode is None: 1bghaefcdi

820 return super().format_help(ctx, formatter) 1bghaefcdi

821 from . import rich_utils 1bghaefcdi

822 

823 return rich_utils.rich_format_help( 1bghaefcdi

824 obj=self, 

825 ctx=ctx, 

826 markup_mode=self.rich_markup_mode, 

827 ) 

828 

829 def list_commands(self, ctx: click.Context) -> List[str]: 1bghaefcdi

830 """Returns a list of subcommand names. 

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

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

833 return [n for n, c in self.commands.items()] 1bghaefcdi