Coverage for typer / core.py: 100%

323 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-09 16:39 +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 | None = None) -> 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) # type: ignore[arg-type] 1aefcdbg

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

392 if type_var: 1aefcdbg

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

394 if self.nargs != 1: 1aefcdbg

395 var += "..." 1aefcdbg

396 return var 1aefcdbg

397 

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

399 return _value_is_missing(self, value) 1aefcdbg

400 

401 

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

403 def __init__( 1aefcdbg

404 self, 

405 *, 

406 # Parameter 

407 param_decls: list[str], 

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

409 required: bool | None = None, 

410 default: Any | None = None, 

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

412 nargs: int | None = None, 

413 metavar: str | None = None, 

414 expose_value: bool = True, 

415 is_eager: bool = False, 

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

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

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

419 shell_complete: Callable[ 

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

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

422 ] 

423 | None = None, 

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

425 # Option 

426 show_default: bool | str = False, 

427 prompt: bool | str = False, 

428 confirmation_prompt: bool | str = False, 

429 prompt_required: bool = True, 

430 hide_input: bool = False, 

431 is_flag: bool | None = None, 

432 multiple: bool = False, 

433 count: bool = False, 

434 allow_from_autoenv: bool = True, 

435 help: str | None = None, 

436 hidden: bool = False, 

437 show_choices: bool = True, 

438 show_envvar: bool = False, 

439 # Rich settings 

440 rich_help_panel: str | None = None, 

441 ): 

442 super().__init__( 1aefcdbg

443 param_decls=param_decls, 

444 type=type, 

445 required=required, 

446 default=default, 

447 callback=callback, 

448 nargs=nargs, 

449 metavar=metavar, 

450 expose_value=expose_value, 

451 is_eager=is_eager, 

452 envvar=envvar, 

453 show_default=show_default, 

454 prompt=prompt, 

455 confirmation_prompt=confirmation_prompt, 

456 hide_input=hide_input, 

457 is_flag=is_flag, 

458 multiple=multiple, 

459 count=count, 

460 allow_from_autoenv=allow_from_autoenv, 

461 help=help, 

462 hidden=hidden, 

463 show_choices=show_choices, 

464 show_envvar=show_envvar, 

465 prompt_required=prompt_required, 

466 shell_complete=shell_complete, 

467 ) 

468 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1aefcdbg

469 self.rich_help_panel = rich_help_panel 1aefcdbg

470 

471 def _get_default_string( 1aefcdbg

472 self, 

473 *, 

474 ctx: click.Context, 

475 show_default_is_str: bool, 

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

477 ) -> str: 

478 return _get_default_string( 1aefcdbg

479 self, 

480 ctx=ctx, 

481 show_default_is_str=show_default_is_str, 

482 default_value=default_value, 

483 ) 

484 

485 def _extract_default_help_str( 1aefcdbg

486 self, *, ctx: click.Context 

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

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

489 

490 def make_metavar(self, ctx: click.Context | None = None) -> str: 1aefcdbg

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

492 

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

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

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

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

497 if self.hidden: 1aefcdbg

498 return None 1aefcdbg

499 

500 any_prefix_is_slash = False 1aefcdbg

501 

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

503 nonlocal any_prefix_is_slash 

504 

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

506 

507 if any_slashes: 1aefcdbg

508 any_prefix_is_slash = True 1aefcdbg

509 

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

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

512 

513 return rv 1aefcdbg

514 

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

516 

517 if self.secondary_opts: 1aefcdbg

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

519 

520 help = self.help or "" 1aefcdbg

521 extra = [] 1aefcdbg

522 

523 if self.show_envvar: 1aefcdbg

524 envvar = self.envvar 1aefcdbg

525 

526 if envvar is None: 1aefcdbg

527 if ( 1ab

528 self.allow_from_autoenv 

529 and ctx.auto_envvar_prefix is not None 

530 and self.name is not None 

531 ): 

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

533 

534 if envvar is not None: 1aefcdbg

535 var_str = ( 1aefcdbg

536 envvar 

537 if isinstance(envvar, str) 

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

539 ) 

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

541 

542 # Typer override: 

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

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

545 # Typer override end 

546 

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

548 

549 if show_default_is_str or ( 1aefcdbg

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

551 ): 

552 # Typer override: 

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

554 default_string = self._get_default_string( 1aefcdbg

555 ctx=ctx, 

556 show_default_is_str=show_default_is_str, 

557 default_value=default_value, 

558 ) 

559 # Typer override end 

560 if default_string: 1aefcdbg

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

562 

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

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

565 

566 if range_str: 1aefcdbg

567 extra.append(range_str) 1aefcdbg

568 

569 if self.required: 1aefcdbg

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

571 

572 if extra: 1aefcdbg

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

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

575 rich_markup_mode = None 1aefcdbg

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

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

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

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

580 from . import rich_utils 1aefcdbg

581 

582 extra_str = rich_utils.escape_before_html_export(extra_str) 1aefcdbg

583 

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

585 

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

587 

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

589 return _value_is_missing(self, value) 1aefcdbg

590 

591 

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

593 if value is None: 1aefcdbg

594 return True 1aefcdbg

595 

596 # Click 8.3 and beyond 

597 # if value is UNSET: 

598 # return True 

599 

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

601 return True # pragma: no cover 

602 

603 return False 1aefcdbg

604 

605 

606def _typer_format_options( 1aefcdbg

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

608) -> None: 

609 args = [] 1aefcdbg

610 opts = [] 1aefcdbg

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

612 rv = param.get_help_record(ctx) 1aefcdbg

613 if rv is not None: 1aefcdbg

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

615 args.append(rv) 1aefcdbg

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

617 opts.append(rv) 1aefcdbg

618 

619 if args: 1aefcdbg

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

621 formatter.write_dl(args) 1aefcdbg

622 if opts: 1aefcdbg

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

624 formatter.write_dl(opts) 1aefcdbg

625 

626 

627def _typer_main_shell_completion( 1aefcdbg

628 self: click.core.Command, 

629 *, 

630 ctx_args: MutableMapping[str, Any], 

631 prog_name: str, 

632 complete_var: str | None = None, 

633) -> None: 

634 if complete_var is None: 1aefcdbg

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

636 

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

638 

639 if not instruction: 1aefcdbg

640 return 1aefcdbg

641 

642 from .completion import shell_complete 1aefcdbg

643 

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

645 sys.exit(rv) 1aefcdbg

646 

647 

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

649 def __init__( 1aefcdbg

650 self, 

651 name: str | None, 

652 *, 

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

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

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

656 help: str | None = None, 

657 epilog: str | None = None, 

658 short_help: str | None = None, 

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

660 add_help_option: bool = True, 

661 no_args_is_help: bool = False, 

662 hidden: bool = False, 

663 deprecated: bool = False, 

664 # Rich settings 

665 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

666 rich_help_panel: str | None = None, 

667 ) -> None: 

668 super().__init__( 1aefcdbg

669 name=name, 

670 context_settings=context_settings, 

671 callback=callback, 

672 params=params, 

673 help=help, 

674 epilog=epilog, 

675 short_help=short_help, 

676 options_metavar=options_metavar, 

677 add_help_option=add_help_option, 

678 no_args_is_help=no_args_is_help, 

679 hidden=hidden, 

680 deprecated=deprecated, 

681 ) 

682 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg

683 self.rich_help_panel = rich_help_panel 1aefcdbg

684 

685 def format_options( 1aefcdbg

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

687 ) -> None: 

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

689 

690 def _main_shell_completion( 1aefcdbg

691 self, 

692 ctx_args: MutableMapping[str, Any], 

693 prog_name: str, 

694 complete_var: str | None = None, 

695 ) -> None: 

696 _typer_main_shell_completion( 1aefcdbg

697 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

698 ) 

699 

700 def main( 1aefcdbg

701 self, 

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

703 prog_name: str | None = None, 

704 complete_var: str | None = None, 

705 standalone_mode: bool = True, 

706 windows_expand_args: bool = True, 

707 **extra: Any, 

708 ) -> Any: 

709 return _main( 1aefcdbg

710 self, 

711 args=args, 

712 prog_name=prog_name, 

713 complete_var=complete_var, 

714 standalone_mode=standalone_mode, 

715 windows_expand_args=windows_expand_args, 

716 rich_markup_mode=self.rich_markup_mode, 

717 **extra, 

718 ) 

719 

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

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

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

723 ctx.ensure_object(dict) 1aefcdbg

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

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

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

727 from . import rich_utils 1aefcdbg

728 

729 return rich_utils.rich_format_help( 1aefcdbg

730 obj=self, 

731 ctx=ctx, 

732 markup_mode=self.rich_markup_mode, 

733 ) 

734 

735 

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

737 def __init__( 1aefcdbg

738 self, 

739 *, 

740 name: str | None = None, 

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

742 # Rich settings 

743 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, 

744 rich_help_panel: str | None = None, 

745 suggest_commands: bool = True, 

746 **attrs: Any, 

747 ) -> None: 

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

749 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg

750 self.rich_help_panel = rich_help_panel 1aefcdbg

751 self.suggest_commands = suggest_commands 1aefcdbg

752 

753 def format_options( 1aefcdbg

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

755 ) -> None: 

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

757 self.format_commands(ctx, formatter) 1aefcdbg

758 

759 def _main_shell_completion( 1aefcdbg

760 self, 

761 ctx_args: MutableMapping[str, Any], 

762 prog_name: str, 

763 complete_var: str | None = None, 

764 ) -> None: 

765 _typer_main_shell_completion( 1aefcdbg

766 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var 

767 ) 

768 

769 def resolve_command( 1aefcdbg

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

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

772 try: 1aefcdbg

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

774 except click.UsageError as e: 1aefcdbg

775 if self.suggest_commands: 1aefcdbg

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

777 if available_commands and args: 1aefcdbg

778 typo = args[0] 1aefcdbg

779 matches = get_close_matches(typo, available_commands) 1aefcdbg

780 if matches: 1aefcdbg

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

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

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

784 raise 1aefcdbg

785 

786 def main( 1aefcdbg

787 self, 

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

789 prog_name: str | None = None, 

790 complete_var: str | None = None, 

791 standalone_mode: bool = True, 

792 windows_expand_args: bool = True, 

793 **extra: Any, 

794 ) -> Any: 

795 return _main( 1aefcdbg

796 self, 

797 args=args, 

798 prog_name=prog_name, 

799 complete_var=complete_var, 

800 standalone_mode=standalone_mode, 

801 windows_expand_args=windows_expand_args, 

802 rich_markup_mode=self.rich_markup_mode, 

803 **extra, 

804 ) 

805 

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

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

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

809 from . import rich_utils 1aefcdbg

810 

811 return rich_utils.rich_format_help( 1aefcdbg

812 obj=self, 

813 ctx=ctx, 

814 markup_mode=self.rich_markup_mode, 

815 ) 

816 

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

818 """Returns a list of subcommand names. 

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

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

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