Coverage for typer / core.py: 100%

323 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-26 21:46 +0000

1import errno 1aefcdbg

2import inspect 1aefcdbg

3import os 1aefcdbg

4import sys 1aefcdbg

5from collections.abc import Callable, MutableMapping, Sequence 1aefcdbg

6from difflib import get_close_matches 1aefcdbg

7from enum import Enum 1aefcdbg

8from gettext import gettext as _ 1aefcdbg

9from typing import ( 1aefcdbg

10 Any, 

11 TextIO, 

12 Union, 

13 cast, 

14) 

15 

16import click 1aefcdbg

17import click.core 1aefcdbg

18import click.formatting 1aefcdbg

19import click.shell_completion 1aefcdbg

20import click.types 1aefcdbg

21import click.utils 1aefcdbg

22 

23from ._typing import Literal 1aefcdbg

24from .utils import parse_boolean_env_var 1aefcdbg

25 

26MarkupMode = Literal["markdown", "rich", None] 1aefcdbg

27MARKUP_MODE_KEY = "TYPER_RICH_MARKUP_MODE" 1aefcdbg

28 

29HAS_RICH = parse_boolean_env_var(os.getenv("TYPER_USE_RICH"), default=True) 1aefcdbg

30 

31if HAS_RICH: 1aefcdbg

32 DEFAULT_MARKUP_MODE: MarkupMode = "rich" 1aefcdbg

33else: 

34 DEFAULT_MARKUP_MODE = None 1aefcdbg

35 

36 

37# Copy from click.parser._split_opt 

38def _split_opt(opt: str) -> tuple[str, str]: 1aefcdbg

39 first = opt[:1] 1aefcdbg

40 if first.isalnum(): 1aefcdbg

41 return "", opt 1aefcdbg

42 if opt[1:2] == first: 1aefcdbg

43 return opt[:2], opt[2:] 1aefcdbg

44 return first, opt[1:] 1aefcdbg

45 

46 

47def _typer_param_setup_autocompletion_compat( 1aefcdbg

48 self: click.Parameter, 

49 *, 

50 autocompletion: Callable[ 

51 [click.Context, list[str], str], list[tuple[str, str] | str] 

52 ] 

53 | None = None, 

54) -> None: 

55 if self._custom_shell_complete is not None: 1aefcdbg

56 import warnings 1aefcdbg

57 

58 warnings.warn( 1aefcdbg

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

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

61 DeprecationWarning, 

62 stacklevel=2, 

63 ) 

64 

65 if autocompletion is not None: 1aefcdbg

66 

67 def compat_autocompletion( 1aefcdbg

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

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

70 from click.shell_completion import CompletionItem 1aefcdbg

71 

72 out = [] 1aefcdbg

73 

74 for c in autocompletion(ctx, [], incomplete): 1aefcdbg

75 if isinstance(c, tuple): 1aefcdbg

76 use_completion = CompletionItem(c[0], help=c[1]) 1aefcdbg

77 else: 

78 assert isinstance(c, str) 1aefcdbg

79 use_completion = CompletionItem(c) 1aefcdbg

80 

81 if use_completion.value.startswith(incomplete): 1aefcdbg

82 out.append(use_completion) 1aefcdbg

83 

84 return out 1aefcdbg

85 

86 self._custom_shell_complete = compat_autocompletion 1aefcdbg

87 

88 

89def _get_default_string( 1aefcdbg

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

91 *, 

92 ctx: click.Context, 

93 show_default_is_str: bool, 

94 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any, 

95) -> str: 

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

97 # rich_utils avoiding RegEx hacks 

98 if show_default_is_str: 1aefcdbg

99 default_string = f"({obj.show_default})" 1aefcdbg

100 elif isinstance(default_value, (list, tuple)): 1aefcdbg

101 default_string = ", ".join( 1aefcdbg

102 _get_default_string( 

103 obj, ctx=ctx, show_default_is_str=show_default_is_str, default_value=d 

104 ) 

105 for d in default_value 

106 ) 

107 elif isinstance(default_value, Enum): 1aefcdbg

108 default_string = str(default_value.value) 1aefcdbg

109 elif inspect.isfunction(default_value): 1aefcdbg

110 default_string = _("(dynamic)") 1aefcdbg

111 elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: 1aefcdbg

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

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

114 # Typer override, original commented 

115 # default_string = click.parser.split_opt( 

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

117 # )[1] 

118 if obj.default: 1aefcdbg

119 if obj.opts: 1aefcdbg

120 default_string = _split_opt(obj.opts[0])[1] 1aefcdbg

121 else: 

122 default_string = str(default_value) 1aefcdbg

123 else: 

124 default_string = _split_opt(obj.secondary_opts[0])[1] 1aefcdbg

125 # Typer override end 

126 elif ( 1ab

127 isinstance(obj, TyperOption) 

128 and obj.is_bool_flag 

129 and not obj.secondary_opts 

130 and not default_value 

131 ): 

132 default_string = "" 1aefcdbg

133 else: 

134 default_string = str(default_value) 1aefcdbg

135 return default_string 1aefcdbg

136 

137 

138def _extract_default_help_str( 1aefcdbg

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

140) -> Any | Callable[[], Any] | None: 

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

142 # rich_utils avoiding RegEx hacks 

143 # Temporarily enable resilient parsing to avoid type casting 

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

145 # help formatting in general. 

146 resilient = ctx.resilient_parsing 1aefcdbg

147 ctx.resilient_parsing = True 1aefcdbg

148 

149 try: 1aefcdbg

150 default_value = obj.get_default(ctx, call=False) 1aefcdbg

151 finally: 

152 ctx.resilient_parsing = resilient 1aefcdbg

153 return default_value 1aefcdbg

154 

155 

156def _main( 1aefcdbg

157 self: click.Command, 

158 *, 

159 args: Sequence[str] | None = None, 

160 prog_name: str | None = None, 

161 complete_var: str | None = None, 

162 standalone_mode: bool = True, 

163 windows_expand_args: bool = True, 

164 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

165 **extra: Any, 

166) -> Any: 

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

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

169 # further execution to avoid a broken script. 

170 if args is None: 1aefcdbg

171 args = sys.argv[1:] 1aefcdbg

172 

173 # Covered in Click tests 

174 if os.name == "nt" and windows_expand_args: # pragma: no cover 1aefcdbg

175 args = click.utils._expand_args(args) 1cd

176 else: 

177 args = list(args) 1aefcdbg

178 

179 if prog_name is None: 1aefcdbg

180 prog_name = click.utils._detect_program_name() 1aefcdbg

181 

182 # Process shell completion requests and exit early. 

183 self._main_shell_completion(extra, prog_name, complete_var) 1aefcdbg

184 

185 try: 1aefcdbg

186 try: 1aefcdbg

187 with self.make_context(prog_name, args, **extra) as ctx: 1aefcdbg

188 rv = self.invoke(ctx) 1aefcdbg

189 if not standalone_mode: 1aefcdbg

190 return rv 1aefcdbg

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

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

193 # has obvious effects 

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

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

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

197 # by its truthiness/falsiness 

198 ctx.exit() 1aefcdbg

199 except EOFError as e: 1aefcdbg

200 click.echo(file=sys.stderr) 1aefcdbg

201 raise click.Abort() from e 1aefcdbg

202 except KeyboardInterrupt as e: 1aefcdbg

203 raise click.exceptions.Exit(130) from e 1aefcdbg

204 except click.ClickException as e: 1aefcdbg

205 if not standalone_mode: 1aefcdbg

206 raise 1aefcdbg

207 # Typer override 

208 if HAS_RICH and rich_markup_mode is not None: 1aefcdbg

209 from . import rich_utils 1aefcdbg

210 

211 rich_utils.rich_format_error(e) 1aefcdbg

212 else: 

213 e.show() 1aefcdbg

214 # Typer override end 

215 sys.exit(e.exit_code) 1aefcdbg

216 except OSError as e: 1aefcdbg

217 if e.errno == errno.EPIPE: 1aefcdbg

218 sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout)) 1aefcdbg

219 sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr)) 1aefcdbg

220 sys.exit(1) 1aefcdbg

221 else: 

222 raise 1aefcdbg

223 except click.exceptions.Exit as e: 1aefcdbg

224 if standalone_mode: 1aefcdbg

225 sys.exit(e.exit_code) 1aefcdbg

226 else: 

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

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

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

230 # would return its result 

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

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

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

234 # tell the difference between the two 

235 return e.exit_code 1aefcdbg

236 except click.Abort: 1aefcdbg

237 if not standalone_mode: 1aefcdbg

238 raise 1aefcdbg

239 # Typer override 

240 if HAS_RICH and rich_markup_mode is not None: 1aefcdbg

241 from . import rich_utils 1aefcdbg

242 

243 rich_utils.rich_abort_error() 1aefcdbg

244 else: 

245 click.echo(_("Aborted!"), file=sys.stderr) 1aefcdbg

246 # Typer override end 

247 sys.exit(1) 1aefcdbg

248 

249 

250class TyperArgument(click.core.Argument): 1aefcdbg

251 def __init__( 1aefcdbg

252 self, 

253 *, 

254 # Parameter 

255 param_decls: list[str], 

256 type: Any | None = None, 

257 required: bool | None = None, 

258 default: Any | None = None, 

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

260 nargs: int | None = None, 

261 metavar: str | None = None, 

262 expose_value: bool = True, 

263 is_eager: bool = False, 

264 envvar: str | list[str] | None = None, 

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

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

267 shell_complete: Callable[ 

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

269 list["click.shell_completion.CompletionItem"] | list[str], 

270 ] 

271 | None = None, 

272 autocompletion: Callable[..., Any] | None = None, 

273 # TyperArgument 

274 show_default: bool | str = True, 

275 show_choices: bool = True, 

276 show_envvar: bool = True, 

277 help: str | None = None, 

278 hidden: bool = False, 

279 # Rich settings 

280 rich_help_panel: str | None = None, 

281 ): 

282 self.help = help 1aefcdbg

283 self.show_default = show_default 1aefcdbg

284 self.show_choices = show_choices 1aefcdbg

285 self.show_envvar = show_envvar 1aefcdbg

286 self.hidden = hidden 1aefcdbg

287 self.rich_help_panel = rich_help_panel 1aefcdbg

288 

289 super().__init__( 1aefcdbg

290 param_decls=param_decls, 

291 type=type, 

292 required=required, 

293 default=default, 

294 callback=callback, 

295 nargs=nargs, 

296 metavar=metavar, 

297 expose_value=expose_value, 

298 is_eager=is_eager, 

299 envvar=envvar, 

300 shell_complete=shell_complete, 

301 ) 

302 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1aefcdbg

303 

304 def _get_default_string( 1aefcdbg

305 self, 

306 *, 

307 ctx: click.Context, 

308 show_default_is_str: bool, 

309 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any, 

310 ) -> str: 

311 return _get_default_string( 1aefcdbg

312 self, 

313 ctx=ctx, 

314 show_default_is_str=show_default_is_str, 

315 default_value=default_value, 

316 ) 

317 

318 def _extract_default_help_str( 1aefcdbg

319 self, *, ctx: click.Context 

320 ) -> Any | Callable[[], Any] | None: 

321 return _extract_default_help_str(self, ctx=ctx) 1aefcdbg

322 

323 def get_help_record(self, ctx: click.Context) -> tuple[str, str] | None: 1aefcdbg

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

325 # to support Arguments 

326 if self.hidden: 1aefcdbg

327 return None 1aefcdbg

328 name = self.make_metavar(ctx=ctx) 1aefcdbg

329 help = self.help or "" 1aefcdbg

330 extra = [] 1aefcdbg

331 if self.show_envvar: 1aefcdbg

332 envvar = self.envvar 1aefcdbg

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

334 if envvar is not None: 1aefcdbg

335 var_str = ( 1aefcdbg

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

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

338 else envvar 

339 ) 

340 extra.append(f"env var: {var_str}") 1aefcdbg

341 

342 # Typer override: 

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

344 default_value = self._extract_default_help_str(ctx=ctx) 1aefcdbg

345 # Typer override end 

346 

347 show_default_is_str = isinstance(self.show_default, str) 1aefcdbg

348 

349 if show_default_is_str or ( 1aefcdbg

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

351 ): 

352 # Typer override: 

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

354 default_string = self._get_default_string( 1aefcdbg

355 ctx=ctx, 

356 show_default_is_str=show_default_is_str, 

357 default_value=default_value, 

358 ) 

359 # Typer override end 

360 if default_string: 1aefcdbg

361 extra.append(_("default: {default}").format(default=default_string)) 1aefcdbg

362 if self.required: 1aefcdbg

363 extra.append(_("required")) 1aefcdbg

364 if extra: 1aefcdbg

365 extra_str = "; ".join(extra) 1aefcdbg

366 extra_str = f"[{extra_str}]" 1aefcdbg

367 rich_markup_mode = None 1aefcdbg

368 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1aefcdbg

369 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1aefcdbg

370 if HAS_RICH and rich_markup_mode == "rich": 1aefcdbg

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

372 from . import rich_utils 1aefcdbg

373 

374 extra_str = rich_utils.escape_before_html_export(extra_str) 1aefcdbg

375 

376 help = f"{help} {extra_str}" if help else f"{extra_str}" 1aefcdbg

377 return name, help 1aefcdbg

378 

379 def make_metavar(self, ctx: click.Context) -> str: 1aefcdbg

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

381 # to include Argument name 

382 if self.metavar is not None: 1aefcdbg

383 var = self.metavar 1aefcdbg

384 if not self.required and not var.startswith("["): 1aefcdbg

385 var = f"[{var}]" 1aefcdbg

386 return var 1aefcdbg

387 var = (self.name or "").upper() 1aefcdbg

388 if not self.required: 1aefcdbg

389 var = f"[{var}]" 1aefcdbg

390 type_var = self.type.get_metavar(self, ctx=ctx) 1aefcdbg

391 if type_var: 1aefcdbg

392 var += f":{type_var}" 1aefcdbg

393 if self.nargs != 1: 1aefcdbg

394 var += "..." 1aefcdbg

395 return var 1aefcdbg

396 

397 def value_is_missing(self, value: Any) -> bool: 1aefcdbg

398 return _value_is_missing(self, value) 1aefcdbg

399 

400 

401class TyperOption(click.core.Option): 1aefcdbg

402 def __init__( 1aefcdbg

403 self, 

404 *, 

405 # Parameter 

406 param_decls: list[str], 

407 type: click.types.ParamType | Any | None = None, 

408 required: bool | None = None, 

409 default: Any | None = None, 

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

411 nargs: int | None = None, 

412 metavar: str | None = None, 

413 expose_value: bool = True, 

414 is_eager: bool = False, 

415 envvar: str | list[str] | None = None, 

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

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

418 shell_complete: Callable[ 

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

420 list["click.shell_completion.CompletionItem"] | list[str], 

421 ] 

422 | None = None, 

423 autocompletion: Callable[..., Any] | None = None, 

424 # Option 

425 show_default: bool | str = False, 

426 prompt: bool | str = False, 

427 confirmation_prompt: bool | str = False, 

428 prompt_required: bool = True, 

429 hide_input: bool = False, 

430 is_flag: bool | None = None, 

431 multiple: bool = False, 

432 count: bool = False, 

433 allow_from_autoenv: bool = True, 

434 help: str | None = None, 

435 hidden: bool = False, 

436 show_choices: bool = True, 

437 show_envvar: bool = False, 

438 # Rich settings 

439 rich_help_panel: str | None = None, 

440 ): 

441 super().__init__( 1aefcdbg

442 param_decls=param_decls, 

443 type=type, 

444 required=required, 

445 default=default, 

446 callback=callback, 

447 nargs=nargs, 

448 metavar=metavar, 

449 expose_value=expose_value, 

450 is_eager=is_eager, 

451 envvar=envvar, 

452 show_default=show_default, 

453 prompt=prompt, 

454 confirmation_prompt=confirmation_prompt, 

455 hide_input=hide_input, 

456 is_flag=is_flag, 

457 multiple=multiple, 

458 count=count, 

459 allow_from_autoenv=allow_from_autoenv, 

460 help=help, 

461 hidden=hidden, 

462 show_choices=show_choices, 

463 show_envvar=show_envvar, 

464 prompt_required=prompt_required, 

465 shell_complete=shell_complete, 

466 ) 

467 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1aefcdbg

468 self.rich_help_panel = rich_help_panel 1aefcdbg

469 

470 def _get_default_string( 1aefcdbg

471 self, 

472 *, 

473 ctx: click.Context, 

474 show_default_is_str: bool, 

475 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any, 

476 ) -> str: 

477 return _get_default_string( 1aefcdbg

478 self, 

479 ctx=ctx, 

480 show_default_is_str=show_default_is_str, 

481 default_value=default_value, 

482 ) 

483 

484 def _extract_default_help_str( 1aefcdbg

485 self, *, ctx: click.Context 

486 ) -> Any | Callable[[], Any] | None: 

487 return _extract_default_help_str(self, ctx=ctx) 1aefcdbg

488 

489 def make_metavar(self, ctx: click.Context) -> str: 1aefcdbg

490 return super().make_metavar(ctx=ctx) 1aefcdbg

491 

492 def get_help_record(self, ctx: click.Context) -> tuple[str, str] | None: 1aefcdbg

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

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

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

496 if self.hidden: 1aefcdbg

497 return None 1aefcdbg

498 

499 any_prefix_is_slash = False 1aefcdbg

500 

501 def _write_opts(opts: Sequence[str]) -> str: 1aefcdbg

502 nonlocal any_prefix_is_slash 

503 

504 rv, any_slashes = click.formatting.join_options(opts) 1aefcdbg

505 

506 if any_slashes: 1aefcdbg

507 any_prefix_is_slash = True 1aefcdbg

508 

509 if not self.is_flag and not self.count: 1aefcdbg

510 rv += f" {self.make_metavar(ctx=ctx)}" 1aefcdbg

511 

512 return rv 1aefcdbg

513 

514 rv = [_write_opts(self.opts)] 1aefcdbg

515 

516 if self.secondary_opts: 1aefcdbg

517 rv.append(_write_opts(self.secondary_opts)) 1aefcdbg

518 

519 help = self.help or "" 1aefcdbg

520 extra = [] 1aefcdbg

521 

522 if self.show_envvar: 1aefcdbg

523 envvar = self.envvar 1aefcdbg

524 

525 if envvar is None: 1aefcdbg

526 if ( 1ab

527 self.allow_from_autoenv 

528 and ctx.auto_envvar_prefix is not None 

529 and self.name is not None 

530 ): 

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

532 

533 if envvar is not None: 1aefcdbg

534 var_str = ( 1aefcdbg

535 envvar 

536 if isinstance(envvar, str) 

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

538 ) 

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

540 

541 # Typer override: 

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

543 default_value = self._extract_default_help_str(ctx=ctx) 1aefcdbg

544 # Typer override end 

545 

546 show_default_is_str = isinstance(self.show_default, str) 1aefcdbg

547 

548 if show_default_is_str or ( 1aefcdbg

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

550 ): 

551 # Typer override: 

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

553 default_string = self._get_default_string( 1aefcdbg

554 ctx=ctx, 

555 show_default_is_str=show_default_is_str, 

556 default_value=default_value, 

557 ) 

558 # Typer override end 

559 if default_string: 1aefcdbg

560 extra.append(_("default: {default}").format(default=default_string)) 1aefcdbg

561 

562 if isinstance(self.type, click.types._NumberRangeBase): 1aefcdbg

563 range_str = self.type._describe_range() 1aefcdbg

564 

565 if range_str: 1aefcdbg

566 extra.append(range_str) 1aefcdbg

567 

568 if self.required: 1aefcdbg

569 extra.append(_("required")) 1aefcdbg

570 

571 if extra: 1aefcdbg

572 extra_str = "; ".join(extra) 1aefcdbg

573 extra_str = f"[{extra_str}]" 1aefcdbg

574 rich_markup_mode = None 1aefcdbg

575 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1aefcdbg

576 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1aefcdbg

577 if HAS_RICH and rich_markup_mode == "rich": 1aefcdbg

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

579 from . import rich_utils 1aefcdbg

580 

581 extra_str = rich_utils.escape_before_html_export(extra_str) 1aefcdbg

582 

583 help = f"{help} {extra_str}" if help else f"{extra_str}" 1aefcdbg

584 

585 return ("; " if any_prefix_is_slash else " / ").join(rv), help 1aefcdbg

586 

587 def value_is_missing(self, value: Any) -> bool: 1aefcdbg

588 return _value_is_missing(self, value) 1aefcdbg

589 

590 

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

592 if value is None: 1aefcdbg

593 return True 1aefcdbg

594 

595 # Click 8.3 and beyond 

596 # if value is UNSET: 

597 # return True 

598 

599 if (param.nargs != 1 or param.multiple) and value == (): 1aefcdbg

600 return True # pragma: no cover 

601 

602 return False 1aefcdbg

603 

604 

605def _typer_format_options( 1aefcdbg

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

607) -> None: 

608 args = [] 1aefcdbg

609 opts = [] 1aefcdbg

610 for param in self.get_params(ctx): 1aefcdbg

611 rv = param.get_help_record(ctx) 1aefcdbg

612 if rv is not None: 1aefcdbg

613 if param.param_type_name == "argument": 1aefcdbg

614 args.append(rv) 1aefcdbg

615 elif param.param_type_name == "option": 1aefcdbg

616 opts.append(rv) 1aefcdbg

617 

618 if args: 1aefcdbg

619 with formatter.section(_("Arguments")): 1aefcdbg

620 formatter.write_dl(args) 1aefcdbg

621 if opts: 1aefcdbg

622 with formatter.section(_("Options")): 1aefcdbg

623 formatter.write_dl(opts) 1aefcdbg

624 

625 

626def _typer_main_shell_completion( 1aefcdbg

627 self: click.core.Command, 

628 *, 

629 ctx_args: MutableMapping[str, Any], 

630 prog_name: str, 

631 complete_var: str | None = None, 

632) -> None: 

633 if complete_var is None: 1aefcdbg

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

635 

636 instruction = os.environ.get(complete_var) 1aefcdbg

637 

638 if not instruction: 1aefcdbg

639 return 1aefcdbg

640 

641 from .completion import shell_complete 1aefcdbg

642 

643 rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) 1aefcdbg

644 sys.exit(rv) 1aefcdbg

645 

646 

647class TyperCommand(click.core.Command): 1aefcdbg

648 def __init__( 1aefcdbg

649 self, 

650 name: str | None, 

651 *, 

652 context_settings: dict[str, Any] | None = None, 

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

654 params: list[click.Parameter] | None = None, 

655 help: str | None = None, 

656 epilog: str | None = None, 

657 short_help: str | None = None, 

658 options_metavar: str | None = "[OPTIONS]", 

659 add_help_option: bool = True, 

660 no_args_is_help: bool = False, 

661 hidden: bool = False, 

662 deprecated: bool = False, 

663 # Rich settings 

664 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

665 rich_help_panel: str | None = None, 

666 ) -> None: 

667 super().__init__( 1aefcdbg

668 name=name, 

669 context_settings=context_settings, 

670 callback=callback, 

671 params=params, 

672 help=help, 

673 epilog=epilog, 

674 short_help=short_help, 

675 options_metavar=options_metavar, 

676 add_help_option=add_help_option, 

677 no_args_is_help=no_args_is_help, 

678 hidden=hidden, 

679 deprecated=deprecated, 

680 ) 

681 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg

682 self.rich_help_panel = rich_help_panel 1aefcdbg

683 

684 def format_options( 1aefcdbg

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

686 ) -> None: 

687 _typer_format_options(self, ctx=ctx, formatter=formatter) 1aefcdbg

688 

689 def _main_shell_completion( 1aefcdbg

690 self, 

691 ctx_args: MutableMapping[str, Any], 

692 prog_name: str, 

693 complete_var: str | None = None, 

694 ) -> None: 

695 _typer_main_shell_completion( 1aefcdbg

696 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

697 ) 

698 

699 def main( 1aefcdbg

700 self, 

701 args: Sequence[str] | None = None, 

702 prog_name: str | None = None, 

703 complete_var: str | None = None, 

704 standalone_mode: bool = True, 

705 windows_expand_args: bool = True, 

706 **extra: Any, 

707 ) -> Any: 

708 return _main( 1aefcdbg

709 self, 

710 args=args, 

711 prog_name=prog_name, 

712 complete_var=complete_var, 

713 standalone_mode=standalone_mode, 

714 windows_expand_args=windows_expand_args, 

715 rich_markup_mode=self.rich_markup_mode, 

716 **extra, 

717 ) 

718 

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

720 if not HAS_RICH or self.rich_markup_mode is None: 1aefcdbg

721 if not hasattr(ctx, "obj") or ctx.obj is None: 1aefcdbg

722 ctx.ensure_object(dict) 1aefcdbg

723 if isinstance(ctx.obj, dict): 1aefcdbg

724 ctx.obj[MARKUP_MODE_KEY] = self.rich_markup_mode 1aefcdbg

725 return super().format_help(ctx, formatter) 1aefcdbg

726 from . import rich_utils 1aefcdbg

727 

728 return rich_utils.rich_format_help( 1aefcdbg

729 obj=self, 

730 ctx=ctx, 

731 markup_mode=self.rich_markup_mode, 

732 ) 

733 

734 

735class TyperGroup(click.core.Group): 1aefcdbg

736 def __init__( 1aefcdbg

737 self, 

738 *, 

739 name: str | None = None, 

740 commands: dict[str, click.Command] | Sequence[click.Command] | None = None, 

741 # Rich settings 

742 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

743 rich_help_panel: str | None = None, 

744 suggest_commands: bool = True, 

745 **attrs: Any, 

746 ) -> None: 

747 super().__init__(name=name, commands=commands, **attrs) 1aefcdbg

748 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg

749 self.rich_help_panel = rich_help_panel 1aefcdbg

750 self.suggest_commands = suggest_commands 1aefcdbg

751 

752 def format_options( 1aefcdbg

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

754 ) -> None: 

755 _typer_format_options(self, ctx=ctx, formatter=formatter) 1aefcdbg

756 self.format_commands(ctx, formatter) 1aefcdbg

757 

758 def _main_shell_completion( 1aefcdbg

759 self, 

760 ctx_args: MutableMapping[str, Any], 

761 prog_name: str, 

762 complete_var: str | None = None, 

763 ) -> None: 

764 _typer_main_shell_completion( 1aefcdbg

765 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

766 ) 

767 

768 def resolve_command( 1aefcdbg

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

770 ) -> tuple[str | None, click.Command | None, list[str]]: 

771 try: 1aefcdbg

772 return super().resolve_command(ctx, args) 1aefcdbg

773 except click.UsageError as e: 1aefcdbg

774 if self.suggest_commands: 1aefcdbg

775 available_commands = list(self.commands.keys()) 1aefcdbg

776 if available_commands and args: 1aefcdbg

777 typo = args[0] 1aefcdbg

778 matches = get_close_matches(typo, available_commands) 1aefcdbg

779 if matches: 1aefcdbg

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

781 message = e.message.rstrip(".") 1aefcdbg

782 e.message = f"{message}. Did you mean {suggestions}?" 1aefcdbg

783 raise 1aefcdbg

784 

785 def main( 1aefcdbg

786 self, 

787 args: Sequence[str] | None = None, 

788 prog_name: str | None = None, 

789 complete_var: str | None = None, 

790 standalone_mode: bool = True, 

791 windows_expand_args: bool = True, 

792 **extra: Any, 

793 ) -> Any: 

794 return _main( 1aefcdbg

795 self, 

796 args=args, 

797 prog_name=prog_name, 

798 complete_var=complete_var, 

799 standalone_mode=standalone_mode, 

800 windows_expand_args=windows_expand_args, 

801 rich_markup_mode=self.rich_markup_mode, 

802 **extra, 

803 ) 

804 

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

806 if not HAS_RICH or self.rich_markup_mode is None: 1aefcdbg

807 return super().format_help(ctx, formatter) 1aefcdbg

808 from . import rich_utils 1aefcdbg

809 

810 return rich_utils.rich_format_help( 1aefcdbg

811 obj=self, 

812 ctx=ctx, 

813 markup_mode=self.rich_markup_mode, 

814 ) 

815 

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

817 """Returns a list of subcommand names. 

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

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

820 return [n for n, c in self.commands.items()] 1aefcdbg