Coverage for typer / _click / core.py: 100%

486 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-09 00:15 +0000

1import enum 1aecfbdg

2import inspect 1aecfbdg

3import os 1aecfbdg

4from abc import ABC, abstractmethod 1aecfbdg

5from collections.abc import ( 1aecfbdg

6 Callable, 

7 Generator, 

8 Iterator, 

9 Mapping, 

10 MutableMapping, 

11 Sequence, 

12) 

13from contextlib import AbstractContextManager, ExitStack, contextmanager 1aecfbdg

14from types import TracebackType 1aecfbdg

15from typing import ( 1aecfbdg

16 TYPE_CHECKING, 

17 Any, 

18 Literal, 

19 NoReturn, 

20 TypeVar, 

21 Union, 

22 cast, 

23 overload, 

24) 

25 

26from ..exceptions import Abort, Exit 1aecfbdg

27from . import types 1aecfbdg

28from .exceptions import BadParameter, MissingParameter, NoArgsIsHelpError, UsageError 1aecfbdg

29from .formatting import HelpFormatter 1aecfbdg

30from .globals import pop_context, push_context 1aecfbdg

31from .parser import _OptionParser 1aecfbdg

32from .termui import style 1aecfbdg

33from .utils import _escape_control_characters, echo, make_default_short_help 1aecfbdg

34 

35if TYPE_CHECKING: 1aecfbdg

36 from ..core import TyperOption 

37 from .shell_completion import CompletionItem 

38 

39F = TypeVar("F", bound="Callable[..., Any]") 1aecfbdg

40V = TypeVar("V") 1aecfbdg

41 

42 

43def _complete_visible_commands( 1aecfbdg

44 ctx: "Context", incomplete: str 

45) -> Iterator[tuple[str, "Command"]]: 

46 """List all the subcommands of a group that start with the 

47 incomplete value and aren't hidden. 

48 """ 

49 # avoid circular imports 

50 from ..core import TyperGroup 1aecfbdg

51 

52 multi = cast(TyperGroup, ctx.command) 1aecfbdg

53 

54 for name in multi.list_commands(ctx): 1aecfbdg

55 if name.startswith(incomplete): 1aecfbdg

56 command = multi.get_command(ctx, name) 1aecfbdg

57 

58 if command is not None and not command.hidden: 1aecfbdg

59 yield name, command 1aecfbdg

60 

61 

62@contextmanager 1aecfbdg

63def augment_usage_errors( 1aecfbdg

64 ctx: "Context", param: Union["Parameter", None] = None 

65) -> Generator[None]: 

66 """Context manager that attaches extra information to exceptions.""" 

67 try: 1aecfbdg

68 yield 1aecfbdg

69 except BadParameter as e: 1aecfbdg

70 if e.ctx is None: 1aecfbdg

71 e.ctx = ctx 1aecfbdg

72 if param is not None and e.param is None: 1aecfbdg

73 e.param = param 1aecfbdg

74 raise 1aecfbdg

75 except UsageError as e: # pragma: no cover 1aecfbdg

76 if e.ctx is None: 

77 e.ctx = ctx 

78 raise 

79 

80 

81def iter_params_for_processing( 1aecfbdg

82 invocation_order: Sequence["Parameter"], 

83 declaration_order: Sequence["Parameter"], 

84) -> list["Parameter"]: 

85 """Returns all declared parameters in the order they should be processed. 

86 

87 The declared parameters are re-shuffled depending on the order in which 

88 they were invoked, as well as the eagerness of each parameters. 

89 

90 The invocation order takes precedence over the declaration order. I.e. the 

91 order in which the user provided them to the CLI is respected. 

92 

93 This behavior and its effect on callback evaluation is detailed at: 

94 https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order 

95 """ 

96 

97 def sort_key(item: Parameter) -> tuple[bool, float]: 1aecfbdg

98 try: 1aecfbdg

99 idx: float = invocation_order.index(item) 1aecfbdg

100 except ValueError: 1aecfbdg

101 idx = float("inf") 1aecfbdg

102 

103 return not item.is_eager, idx 1aecfbdg

104 

105 return sorted(declaration_order, key=sort_key) 1aecfbdg

106 

107 

108class ParameterSource(enum.Enum): 1aecfbdg

109 """This is an `Enum` that indicates the source of a 

110 parameter's value. 

111 """ 

112 

113 COMMANDLINE = enum.auto() 1aecfbdg

114 """The value was provided by the command line args.""" 1aecfbdg

115 ENVIRONMENT = enum.auto() 1aecfbdg

116 """The value was provided with an environment variable.""" 1aecfbdg

117 DEFAULT = enum.auto() 1aecfbdg

118 """Used the default specified by the parameter.""" 1aecfbdg

119 DEFAULT_MAP = enum.auto() 1aecfbdg

120 """Used a default provided by `Context.default_map`.""" 1aecfbdg

121 PROMPT = enum.auto() 1aecfbdg

122 """Used a prompt to confirm a default or provide a value.""" 1aecfbdg

123 

124 

125class Context: 1aecfbdg

126 """The context is a special internal object that holds state relevant 

127 for the script execution at every single level. It's normally invisible 

128 to commands unless they opt-in to getting access to it. 

129 

130 The context is useful as it can pass internal objects around and can 

131 control special execution features such as reading data from 

132 environment variables. 

133 

134 A context can be used as context manager in which case it will call 

135 `close` on teardown. 

136 """ 

137 

138 formatter_class: type[HelpFormatter] = HelpFormatter 1aecfbdg

139 

140 def __init__( 1aecfbdg

141 self, 

142 command: "Command", 

143 parent: Union["Context", None] = None, 

144 info_name: str | None = None, 

145 obj: Any | None = None, 

146 auto_envvar_prefix: str | None = None, 

147 default_map: MutableMapping[str, Any] | None = None, 

148 terminal_width: int | None = None, 

149 max_content_width: int | None = None, 

150 resilient_parsing: bool = False, 

151 allow_extra_args: bool | None = None, 

152 allow_interspersed_args: bool | None = None, 

153 ignore_unknown_options: bool | None = None, 

154 help_option_names: list[str] | None = None, 

155 token_normalize_func: Callable[[str], str] | None = None, 

156 color: bool | None = None, 

157 show_default: bool | None = None, 

158 ) -> None: 

159 self.parent = parent 1aecfbdg

160 self.command = command 1aecfbdg

161 self.info_name = info_name 1aecfbdg

162 # Map of parameter names to their parsed values. 

163 self.params: dict[str, Any] = {} 1aecfbdg

164 # the leftover arguments. 

165 self.args: list[str] = [] 1aecfbdg

166 # protected arguments. used to implement nested parsing. 

167 self._protected_args: list[str] = [] 1aecfbdg

168 # the collected prefixes of the command's options. 

169 self._opt_prefixes: set[str] = set(parent._opt_prefixes) if parent else set() 1aecfbdg

170 

171 if obj is None and parent is not None: 1aecfbdg

172 obj = parent.obj 1aecfbdg

173 

174 self.obj: Any = obj 1aecfbdg

175 self._meta: dict[str, Any] = getattr(parent, "meta", {}) 1aecfbdg

176 

177 # A dictionary (-like object) with defaults for parameters. 

178 if ( 

179 default_map is None 

180 and info_name is not None 

181 and parent is not None 

182 and parent.default_map is not None 

183 ): 

184 default_map = parent.default_map.get(info_name) 1aecfbdg

185 

186 self.default_map: MutableMapping[str, Any] | None = default_map 1aecfbdg

187 

188 # This flag indicates if a subcommand is going to be executed. 

189 self.invoked_subcommand: str | None = None 1aecfbdg

190 

191 if terminal_width is None and parent is not None: 1aecfbdg

192 terminal_width = parent.terminal_width 1aecfbdg

193 

194 # The width of the terminal (None is autodetection). 

195 self.terminal_width: int | None = terminal_width 1aecfbdg

196 

197 if max_content_width is None and parent is not None: 1aecfbdg

198 max_content_width = parent.max_content_width 1aecfbdg

199 

200 self.max_content_width: int | None = max_content_width 1aecfbdg

201 

202 if allow_extra_args is None: 1aecfbdg

203 allow_extra_args = command.allow_extra_args 1aecfbdg

204 

205 self.allow_extra_args = allow_extra_args 1aecfbdg

206 

207 if allow_interspersed_args is None: 1aecfbdg

208 allow_interspersed_args = command.allow_interspersed_args 1aecfbdg

209 

210 self.allow_interspersed_args: bool = allow_interspersed_args 1aecfbdg

211 

212 if ignore_unknown_options is None: 1aecfbdg

213 ignore_unknown_options = command.ignore_unknown_options 1aecfbdg

214 

215 self.ignore_unknown_options: bool = ignore_unknown_options 1aecfbdg

216 

217 if help_option_names is None: 1aecfbdg

218 if parent is not None: 1aecfbdg

219 help_option_names = parent.help_option_names 1aecfbdg

220 else: 

221 help_option_names = ["--help"] 1aecfbdg

222 

223 self.help_option_names: list[str] = help_option_names 1aecfbdg

224 

225 if token_normalize_func is None and parent is not None: 1aecfbdg

226 token_normalize_func = parent.token_normalize_func 1aecfbdg

227 

228 # An optional normalization function for tokens. (options, choices, commands etc.) 

229 self.token_normalize_func: Callable[[str], str] | None = token_normalize_func 1aecfbdg

230 

231 # Indicates if resilient parsing is enabled. 

232 self.resilient_parsing: bool = resilient_parsing 1aecfbdg

233 

234 # If there is no envvar prefix yet, but the parent has one and 

235 # the command on this level has a name, we can expand the envvar 

236 # prefix automatically. 

237 if auto_envvar_prefix is None: 1aecfbdg

238 if ( 

239 parent is not None 

240 and parent.auto_envvar_prefix is not None 

241 and self.info_name is not None 

242 ): 

243 auto_envvar_prefix = ( 1aecfbdg

244 f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" 

245 ) 

246 else: 

247 auto_envvar_prefix = auto_envvar_prefix.upper() 1aecfbdg

248 

249 if auto_envvar_prefix is not None: 1aecfbdg

250 auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") 1aecfbdg

251 

252 self.auto_envvar_prefix: str | None = auto_envvar_prefix 1aecfbdg

253 

254 if color is None and parent is not None: 1aecfbdg

255 color = parent.color 1aecfbdg

256 

257 # Controls if styling output is wanted or not. 

258 self.color: bool | None = color 1aecfbdg

259 

260 if show_default is None and parent is not None: 1aecfbdg

261 show_default = parent.show_default 1aecfbdg

262 

263 # Show option default values when formatting help text. 

264 self.show_default: bool | None = show_default 1aecfbdg

265 

266 self._close_callbacks: list[Callable[[], Any]] = [] 1aecfbdg

267 self._depth = 0 1aecfbdg

268 self._parameter_source: dict[str, ParameterSource] = {} 1aecfbdg

269 self._exit_stack = ExitStack() 1aecfbdg

270 

271 def __enter__(self) -> "Context": 1aecfbdg

272 self._depth += 1 1aecfbdg

273 push_context(self) 1aecfbdg

274 return self 1aecfbdg

275 

276 def __exit__( 1aecfbdg

277 self, 

278 exc_type: type[BaseException] | None, 

279 exc_value: BaseException | None, 

280 tb: TracebackType | None, 

281 ) -> bool | None: 

282 self._depth -= 1 1aecfbdg

283 exit_result: bool | None = None 1aecfbdg

284 if self._depth == 0: 1aecfbdg

285 exit_result = self._close_with_exception_info(exc_type, exc_value, tb) 1aecfbdg

286 pop_context() 1aecfbdg

287 

288 return exit_result 1aecfbdg

289 

290 @contextmanager 1aecfbdg

291 def scope(self, cleanup: bool = True) -> Generator["Context"]: 1aecfbdg

292 """This helper method can be used with the context object to promote 

293 it to the current thread local (see `get_current_context`). 

294 The default behavior of this is to invoke the cleanup functions which 

295 can be disabled by setting `cleanup` to `False`. The cleanup 

296 functions are typically used for things such as closing file handles. 

297 

298 If the cleanup is intended the context object can also be directly 

299 used as a context manager. 

300 """ 

301 if not cleanup: 1aecfbdg

302 self._depth += 1 1aecfbdg

303 try: 1aecfbdg

304 with self as rv: 1aecfbdg

305 yield rv 1aecfbdg

306 finally: 

307 if not cleanup: 1aecfbdg

308 self._depth -= 1 1aecfbdg

309 

310 @property 1aecfbdg

311 def meta(self) -> dict[str, Any]: 1aecfbdg

312 """This is a dictionary which is shared with all the contexts 

313 that are nested. It exists so that click utilities can store some 

314 state here if they need to. It is however the responsibility of 

315 that code to manage this dictionary well. 

316 

317 The keys are supposed to be unique dotted strings. For instance 

318 module paths are a good choice for it. What is stored in there is 

319 irrelevant for the operation of click. However what is important is 

320 that code that places data here adheres to the general semantics of 

321 the system. 

322 """ 

323 return self._meta 1aecfbdg

324 

325 def make_formatter(self) -> HelpFormatter: 1aecfbdg

326 """Creates the HelpFormatter for the help and 

327 usage output. 

328 """ 

329 return self.formatter_class( 1aecfbdg

330 width=self.terminal_width, max_width=self.max_content_width 

331 ) 

332 

333 def with_resource(self, context_manager: AbstractContextManager[V]) -> V: 1aecfbdg

334 """Register a resource as if it were used in a ``with`` 

335 statement. The resource will be cleaned up when the context is 

336 popped. 

337 

338 Uses `contextlib.ExitStack.enter_context`. It calls the 

339 resource's ``__enter__()`` method and returns the result. When 

340 the context is popped, it closes the stack, which calls the 

341 resource's ``__exit__()`` method. 

342 

343 To register a cleanup function for something that isn't a 

344 context manager, use `call_on_close`. Or use something 

345 from `contextlib` to turn it into a context manager first. 

346 """ 

347 return self._exit_stack.enter_context(context_manager) 1aecfbdg

348 

349 def call_on_close(self, f: Callable[..., Any]) -> Callable[..., Any]: 1aecfbdg

350 """Register a function to be called when the context tears down. 

351 

352 This can be used to close resources opened during the script 

353 execution. Resources that support Python's context manager 

354 protocol which would be used in a ``with`` statement should be 

355 registered with `with_resource` instead. 

356 """ 

357 return self._exit_stack.callback(f) 1aecfbdg

358 

359 def close(self) -> None: 1aecfbdg

360 """Invoke all close callbacks registered with `call_on_close`, 

361 and exit all context managers entered with `with_resource`. 

362 """ 

363 self._close_with_exception_info(None, None, None) 1aecfbdg

364 

365 def _close_with_exception_info( 1aecfbdg

366 self, 

367 exc_type: type[BaseException] | None, 

368 exc_value: BaseException | None, 

369 tb: TracebackType | None, 

370 ) -> bool | None: 

371 """Unwind the exit stack by calling its `__exit__` providing the exception 

372 information to allow for exception handling by the various resources registered 

373 using `with_resource` 

374 """ 

375 exit_result = self._exit_stack.__exit__(exc_type, exc_value, tb) 1aecfbdg

376 # In case the context is reused, create a new exit stack. 

377 self._exit_stack = ExitStack() 1aecfbdg

378 

379 return exit_result 1aecfbdg

380 

381 @property 1aecfbdg

382 def command_path(self) -> str: 1aecfbdg

383 """The computed command path. This is used for the ``usage`` 

384 information on the help page. It's automatically created by 

385 combining the info names of the chain of contexts to the root. 

386 """ 

387 rv = "" 1aecfbdg

388 if self.info_name is not None: 1aecfbdg

389 rv = self.info_name 1aecfbdg

390 if self.parent is not None: 1aecfbdg

391 parent_command_path = [self.parent.command_path] 1aecfbdg

392 

393 if isinstance(self.parent.command, Command): 1aecfbdg

394 for param in self.parent.command.get_params(self): 1aecfbdg

395 parent_command_path.extend(param.get_usage_pieces(self)) 1aecfbdg

396 

397 rv = f"{' '.join(parent_command_path)} {rv}" 1aecfbdg

398 return rv.lstrip() 1aecfbdg

399 

400 def find_root(self) -> "Context": 1aecfbdg

401 """Finds the outermost context.""" 

402 node = self 1aecfbdg

403 while node.parent is not None: 1aecfbdg

404 node = node.parent 1aecfbdg

405 return node 1aecfbdg

406 

407 def find_object(self, object_type: type[V]) -> V | None: 1aecfbdg

408 """Finds the closest object of a given type.""" 

409 node: Context | None = self 1aecfbdg

410 

411 while node is not None: 1aecfbdg

412 if isinstance(node.obj, object_type): 1aecfbdg

413 return node.obj 1aecfbdg

414 

415 node = node.parent 1aecfbdg

416 

417 return None 1aecfbdg

418 

419 def ensure_object(self, object_type: type[V]) -> V: 1aecfbdg

420 """Like `find_object` but sets the innermost object to a 

421 new instance of `object_type` if it does not exist. 

422 """ 

423 rv = self.find_object(object_type) 1aecfbdg

424 if rv is None: 1aecfbdg

425 self.obj = rv = object_type() 1aecfbdg

426 return rv 1aecfbdg

427 

428 @overload 1aecfbdg

429 def lookup_default(self, name: str, call: Literal[True] = True) -> Any | None: ... 1aecfbdg

430 

431 @overload 1aecfbdg

432 def lookup_default( 1aecfbdg

433 self, name: str, call: Literal[False] = ... 1acbd

434 ) -> Any | Callable[[], Any] | None: ... 1acbd

435 

436 def lookup_default(self, name: str, call: bool = True) -> Any | None: 1aecfbdg

437 """Get the default for a parameter from `default_map`.""" 

438 if self.default_map is not None: 1aecfbdg

439 value = self.default_map.get(name) 1aecfbdg

440 

441 if call and callable(value): 1aecfbdg

442 return value() 1aecfbdg

443 

444 return value 1aecfbdg

445 

446 return None 1aecfbdg

447 

448 def fail(self, message: str) -> NoReturn: 1aecfbdg

449 """Aborts the execution of the program with a specific error 

450 message. 

451 """ 

452 raise UsageError(message, self) 1aecfbdg

453 

454 def abort(self) -> NoReturn: 1aecfbdg

455 """Aborts the script.""" 

456 raise Abort() 1aecfbdg

457 

458 def exit(self, code: int = 0) -> NoReturn: 1aecfbdg

459 """Exits the application with a given exit code.""" 

460 self.close() 1aecfbdg

461 raise Exit(code) 1aecfbdg

462 

463 def get_usage(self) -> str: 1aecfbdg

464 """Helper method to get formatted usage string for the current 

465 context and command. 

466 """ 

467 return self.command.get_usage(self) 1aecfbdg

468 

469 def get_help(self) -> str: 1aecfbdg

470 """Helper method to get formatted help page for the current 

471 context and command. 

472 """ 

473 return self.command.get_help(self) 1aecfbdg

474 

475 def invoke(self, callback: Callable[..., V], /, *args: Any, **kwargs: Any) -> V: 1aecfbdg

476 """Invokes a command callback in exactly the way it expects. There 

477 are two ways to invoke this method: 

478 

479 1. the first argument can be a callback and all other arguments and 

480 keyword arguments are forwarded directly to the function. 

481 2. the first argument is a click command object. In that case all 

482 arguments are forwarded as well but proper click parameters 

483 (options and click arguments) must be keyword arguments and Click 

484 will fill in defaults. 

485 """ 

486 ctx = self 1aecfbdg

487 

488 with augment_usage_errors(self): 1aecfbdg

489 with ctx: 1aecfbdg

490 return callback(*args, **kwargs) 1aecfbdg

491 

492 def set_parameter_source(self, name: str, source: ParameterSource) -> None: 1aecfbdg

493 """Set the source of a parameter. This indicates the location 

494 from which the value of the parameter was obtained. 

495 """ 

496 self._parameter_source[name] = source 1aecfbdg

497 

498 def get_parameter_source(self, name: str) -> ParameterSource | None: 1aecfbdg

499 """Get the source of a parameter. This indicates the location 

500 from which the value of the parameter was obtained. 

501 

502 This can be useful for determining when a user specified a value 

503 on the command line that is the same as the default value. It 

504 will be `ParameterSource.DEFAULT` only if the 

505 value was actually taken from the default. 

506 """ 

507 return self._parameter_source.get(name) 1aecfbdg

508 

509 

510class Command(ABC): 1aecfbdg

511 """Commands are the basic building block of command line interfaces in 

512 Click. A basic command handles command line parsing and might dispatch 

513 more parsing to commands nested below it. 

514 """ 

515 

516 context_class: type[Context] = Context 1aecfbdg

517 allow_extra_args = False 1aecfbdg

518 allow_interspersed_args = True 1aecfbdg

519 ignore_unknown_options = False 1aecfbdg

520 

521 def __init__( 1aecfbdg

522 self, 

523 name: str | None, 

524 context_settings: MutableMapping[str, Any] | None = None, 

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

526 params: list["Parameter"] | None = None, 

527 help: str | None = None, 

528 epilog: str | None = None, 

529 short_help: str | None = None, 

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

531 add_help_option: bool = True, 

532 no_args_is_help: bool = False, 

533 hidden: bool = False, 

534 deprecated: bool | str = False, 

535 ) -> None: 

536 self.name = name 1aecfbdg

537 

538 if context_settings is None: 1aecfbdg

539 context_settings = {} 1aecfbdg

540 

541 self.context_settings: MutableMapping[str, Any] = context_settings 1aecfbdg

542 

543 self.callback = callback 1aecfbdg

544 self.params: list[Parameter] = params or [] 1aecfbdg

545 self.help = help 1aecfbdg

546 self.epilog = epilog 1aecfbdg

547 self.options_metavar = options_metavar 1aecfbdg

548 self.short_help = short_help 1aecfbdg

549 self.add_help_option = add_help_option 1aecfbdg

550 self._help_option: TyperOption | None = None 1aecfbdg

551 self.no_args_is_help = no_args_is_help 1aecfbdg

552 self.hidden = hidden 1aecfbdg

553 self.deprecated = deprecated 1aecfbdg

554 

555 def __repr__(self) -> str: 1aecfbdg

556 return f"<{self.__class__.__name__} {self.name}>" 1aecfbdg

557 

558 def get_usage(self, ctx: Context) -> str: 1aecfbdg

559 """Formats the usage line into a string and returns it.""" 

560 formatter = ctx.make_formatter() 1aecfbdg

561 self.format_usage(ctx, formatter) 1aecfbdg

562 return formatter.getvalue().rstrip("\n") 1aecfbdg

563 

564 def get_params(self, ctx: Context) -> list["Parameter"]: 1aecfbdg

565 params = self.params 1aecfbdg

566 help_option = self.get_help_option(ctx) 1aecfbdg

567 

568 if help_option is not None: 1aecfbdg

569 params = [*params, help_option] 1aecfbdg

570 

571 return params 1aecfbdg

572 

573 def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: 1aecfbdg

574 """Writes the usage line into the formatter.""" 

575 pieces = self.collect_usage_pieces(ctx) 1aecfbdg

576 formatter.write_usage(ctx.command_path, " ".join(pieces)) 1aecfbdg

577 

578 def collect_usage_pieces(self, ctx: Context) -> list[str]: 1aecfbdg

579 """Returns all the pieces that go into the usage line and returns 

580 it as a list of strings. 

581 """ 

582 rv = [self.options_metavar] if self.options_metavar else [] 1aecfbdg

583 

584 for param in self.get_params(ctx): 1aecfbdg

585 rv.extend(param.get_usage_pieces(ctx)) 1aecfbdg

586 

587 return rv 1aecfbdg

588 

589 def get_help_option_names(self, ctx: Context) -> list[str]: 1aecfbdg

590 """Returns the names for the help option.""" 

591 all_names = set(ctx.help_option_names) 1aecfbdg

592 for param in self.params: 1aecfbdg

593 all_names.difference_update(param.opts) 1aecfbdg

594 all_names.difference_update(param.secondary_opts) 1aecfbdg

595 return list(all_names) 1aecfbdg

596 

597 def get_help_option(self, ctx: Context) -> Union["TyperOption", None]: 1aecfbdg

598 """Returns the help option object.""" 

599 help_option_names = self.get_help_option_names(ctx) 1aecfbdg

600 

601 if not help_option_names or not self.add_help_option: 1aecfbdg

602 return None 1aecfbdg

603 

604 # Cache the help option object in private _help_option attribute to 

605 # avoid creating it multiple times. Not doing this will break the 

606 # callback ordering by iter_params_for_processing(), which relies on 

607 # object comparison. 

608 if self._help_option is None: 1aecfbdg

609 # Avoid circular import. 

610 from .decorators import help_option 1aecfbdg

611 

612 # Apply help_option decorator and pop resulting option 

613 help_option(help_option_names)(self) 1aecfbdg

614 self._help_option = cast("TyperOption", self.params.pop()) 1aecfbdg

615 

616 return self._help_option 1aecfbdg

617 

618 def make_parser(self, ctx: Context) -> _OptionParser: 1aecfbdg

619 """Creates the underlying option parser for this command.""" 

620 parser = _OptionParser(ctx) 1aecfbdg

621 for param in self.get_params(ctx): 1aecfbdg

622 param.add_to_parser(parser, ctx) 1aecfbdg

623 return parser 1aecfbdg

624 

625 def get_help(self, ctx: Context) -> str: 1aecfbdg

626 """Formats the help into a string and returns it.""" 

627 formatter = ctx.make_formatter() 1aecfbdg

628 self.format_help(ctx, formatter) 1aecfbdg

629 return formatter.getvalue().rstrip("\n") 1aecfbdg

630 

631 def get_short_help_str(self, limit: int = 45) -> str: 1aecfbdg

632 """Gets short help for the command or makes it by shortening the 

633 long help string. 

634 """ 

635 if self.short_help: 1aecfbdg

636 text = inspect.cleandoc(self.short_help) 1aecfbdg

637 elif self.help: 1aecfbdg

638 text = make_default_short_help(self.help, limit) 1aecfbdg

639 else: 

640 text = "" 1aecfbdg

641 

642 if self.deprecated: 1aecfbdg

643 deprecated_message = ( 1aecfbdg

644 f"(DEPRECATED: {self.deprecated})" 

645 if isinstance(self.deprecated, str) 

646 else "(DEPRECATED)" 

647 ) 

648 text = f"{text} {deprecated_message}" 1aecfbdg

649 

650 return text.strip() 1aecfbdg

651 

652 def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: 1aecfbdg

653 """Writes the help into the formatter if it exists.""" 

654 self.format_usage(ctx, formatter) 1aecfbdg

655 self.format_help_text(ctx, formatter) 1aecfbdg

656 self.format_options(ctx, formatter) 1aecfbdg

657 self.format_epilog(ctx, formatter) 1aecfbdg

658 

659 def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: 1aecfbdg

660 """Writes the help text to the formatter if it exists.""" 

661 if self.help is not None: 1aecfbdg

662 # truncate the help text to the first form feed 

663 text = inspect.cleandoc(self.help).partition("\f")[0] 1aecfbdg

664 else: 

665 text = "" 1aecfbdg

666 

667 if self.deprecated: 1aecfbdg

668 deprecated_message = ( 1aecfbdg

669 f"(DEPRECATED: {self.deprecated})" 

670 if isinstance(self.deprecated, str) 

671 else "(DEPRECATED)" 

672 ) 

673 text = f"{text} {deprecated_message}" 1aecfbdg

674 

675 if text: 1aecfbdg

676 formatter.write_paragraph() 1aecfbdg

677 

678 with formatter.indentation(): 1aecfbdg

679 formatter.write_text(text) 1aecfbdg

680 

681 @abstractmethod 1aecfbdg

682 def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: 1aecfbdg

683 pass # pragma: no cover 

684 

685 def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: 1aecfbdg

686 """Writes the epilog into the formatter if it exists.""" 

687 if self.epilog: 1aecfbdg

688 epilog = inspect.cleandoc(self.epilog) 1aecfbdg

689 formatter.write_paragraph() 1aecfbdg

690 

691 with formatter.indentation(): 1aecfbdg

692 formatter.write_text(epilog) 1aecfbdg

693 

694 def make_context( 1aecfbdg

695 self, 

696 info_name: str | None, 

697 args: list[str], 

698 parent: Context | None = None, 

699 **extra: Any, 

700 ) -> Context: 

701 """This function when given an info name and arguments will kick 

702 off the parsing and create a new `Context`. It does not 

703 invoke the actual command callback though. 

704 

705 To quickly customize the context class used without overriding 

706 this method, set the `context_class` attribute. 

707 """ 

708 for key, value in self.context_settings.items(): 1aecfbdg

709 if key not in extra: 1aecfbdg

710 extra[key] = value 1aecfbdg

711 

712 ctx = self.context_class(self, info_name=info_name, parent=parent, **extra) 1aecfbdg

713 

714 with ctx.scope(cleanup=False): 1aecfbdg

715 self.parse_args(ctx, args) 1aecfbdg

716 return ctx 1aecfbdg

717 

718 def parse_args(self, ctx: Context, args: list[str]) -> list[str]: 1aecfbdg

719 if not args and self.no_args_is_help and not ctx.resilient_parsing: 1aecfbdg

720 raise NoArgsIsHelpError(ctx) # pragma: no cover 

721 

722 parser = self.make_parser(ctx) 1aecfbdg

723 opts, args, param_order = parser.parse_args(args=args) 1aecfbdg

724 

725 for param in iter_params_for_processing(param_order, self.get_params(ctx)): 1aecfbdg

726 _, args = param.handle_parse_result(ctx, opts, args) 1aecfbdg

727 

728 if args and not ctx.allow_extra_args and not ctx.resilient_parsing: 1aecfbdg

729 extra_args = " ".join(_escape_control_characters(str(arg)) for arg in args) 1aecfbdg

730 ctx.fail(f"Got unexpected extra argument(s) ({extra_args})") 1aecfbdg

731 

732 ctx.args = args 1aecfbdg

733 ctx._opt_prefixes.update(parser._opt_prefixes) 1aecfbdg

734 return args 1aecfbdg

735 

736 def invoke(self, ctx: Context) -> Any: 1aecfbdg

737 """Given a context, this invokes the attached callback (if it exists) 

738 in the right way. 

739 """ 

740 if self.deprecated: 1aecfbdg

741 extra_message = ( 1aecfbdg

742 f" {self.deprecated}" if isinstance(self.deprecated, str) else "" 

743 ) 

744 message = f"DeprecationWarning: The command {self.name!r} is deprecated.{extra_message}" 1aecfbdg

745 echo(style(message, fg="red"), err=True) 1aecfbdg

746 

747 if self.callback is not None: 1aecfbdg

748 return ctx.invoke(self.callback, **ctx.params) 1aecfbdg

749 

750 def shell_complete(self, ctx: Context, incomplete: str) -> list["CompletionItem"]: 1aecfbdg

751 """Return a list of completions for the incomplete value. Looks 

752 at the names of options and chained multi-commands. 

753 

754 Any command could be part of a chained multi-command, so sibling 

755 commands are valid at any point during command completion. 

756 """ 

757 # avoid circular imports 

758 from .shell_completion import CompletionItem 1aecfbdg

759 

760 results: list[CompletionItem] = [] 1aecfbdg

761 

762 if incomplete and not incomplete[0].isalnum(): 1aecfbdg

763 # avoid circular imports 

764 from ..core import TyperOption 1aecfbdg

765 

766 for param in self.get_params(ctx): 1aecfbdg

767 if ( 1ab

768 not isinstance(param, TyperOption) 

769 or param.hidden 

770 or ( 

771 not param.multiple 

772 and ctx.get_parameter_source(param.name) # type: ignore 

773 is ParameterSource.COMMANDLINE 

774 ) 

775 ): 

776 continue 1aecfbdg

777 

778 results.extend( 1aecfbdg

779 CompletionItem(name, help=param.help) 

780 for name in [*param.opts, *param.secondary_opts] 

781 if name.startswith(incomplete) 

782 ) 

783 

784 return results 1aecfbdg

785 

786 @abstractmethod 1aecfbdg

787 def main( 1aecfbdg

788 self, 

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

790 prog_name: str | None = None, 

791 complete_var: str | None = None, 

792 standalone_mode: bool = True, 

793 windows_expand_args: bool = True, 

794 **extra: Any, 

795 ) -> Any: 

796 pass # pragma: no cover 

797 

798 @abstractmethod 1aecfbdg

799 def _main_shell_completion( 1aecfbdg

800 self, 

801 ctx_args: MutableMapping[str, Any], 

802 prog_name: str, 

803 complete_var: str | None = None, 

804 ) -> None: 

805 pass # pragma: no cover 

806 

807 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1aecfbdg

808 """Alias for self.main""" 

809 return self.main(*args, **kwargs) 1aecfbdg

810 

811 

812class Parameter(ABC): 1aecfbdg

813 r"""A parameter to a command comes in two versions: they are either 

814 `Option`\s or `Argument`\s. 

815 

816 Some settings are supported by both options and arguments. 

817 """ 

818 

819 param_type_name = "parameter" 1aecfbdg

820 

821 def __init__( 1aecfbdg

822 self, 

823 param_decls: Sequence[str] | None = None, 

824 type: types.ParamType | Any | None = None, 

825 required: bool = False, 

826 default: Any | Callable[[], Any] | None = None, 

827 callback: Callable[[Context, "Parameter", Any], Any] | None = None, 

828 nargs: int | None = None, 

829 multiple: bool = False, 

830 metavar: str | None = None, 

831 expose_value: bool = True, 

832 is_eager: bool = False, 

833 envvar: str | Sequence[str] | None = None, 

834 shell_complete: Callable[ 

835 [Context, "Parameter", str], list["CompletionItem"] | list[str] 

836 ] 

837 | None = None, 

838 ) -> None: 

839 self.name: str | None 1aecfbdg

840 self.opts: list[str] 1aecfbdg

841 self.secondary_opts: list[str] 1aecfbdg

842 self.name, self.opts, self.secondary_opts = self._parse_decls( 1aecfbdg

843 param_decls or (), expose_value 

844 ) 

845 self.type: types.ParamType = types.convert_type(type, default) 1aecfbdg

846 

847 # Default nargs to what the type tells us if we have that 

848 # information available. 

849 if nargs is None: 1aecfbdg

850 if self.type.is_composite: 1aecfbdg

851 nargs = self.type.arity 1aecfbdg

852 else: 

853 nargs = 1 1aecfbdg

854 

855 self.required = required 1aecfbdg

856 self.callback = callback 1aecfbdg

857 self.nargs = nargs 1aecfbdg

858 self.multiple = multiple 1aecfbdg

859 self.expose_value = expose_value 1aecfbdg

860 self.default: Any | Callable[[], Any] | None = default 1aecfbdg

861 self.is_eager = is_eager 1aecfbdg

862 self.metavar = metavar 1aecfbdg

863 self.envvar = envvar 1aecfbdg

864 self._custom_shell_complete = shell_complete 1aecfbdg

865 

866 def __repr__(self) -> str: 1aecfbdg

867 return f"<{self.__class__.__name__} {self.name}>" 1acbd

868 

869 @abstractmethod 1aecfbdg

870 def _parse_decls( 1aecfbdg

871 self, decls: Sequence[str], expose_value: bool 

872 ) -> tuple[str | None, list[str], list[str]]: 

873 pass # pragma: no cover 

874 

875 @property 1aecfbdg

876 def human_readable_name(self) -> str: 1aecfbdg

877 """Returns the human readable name of this parameter. This is the 

878 same as the name for options, but the metavar for arguments. 

879 """ 

880 assert self.name is not None, "self.name should be set" 1aecfbdg

881 return self.name 1aecfbdg

882 

883 def make_metavar(self, ctx: Context) -> str: 1aecfbdg

884 if self.metavar is not None: 1aecfbdg

885 return self.metavar 1aecfbdg

886 

887 metavar = self.type.get_metavar(param=self, ctx=ctx) 1aecfbdg

888 

889 if metavar is None: 1aecfbdg

890 type_name = self.type.name 1aecfbdg

891 if type_name.startswith("<") and type_name.endswith(">"): 1aecfbdg

892 metavar = type_name 1aecfbdg

893 else: 

894 metavar = f"<{type_name}>" 1aecfbdg

895 

896 if self.nargs != 1: 1aecfbdg

897 metavar += "..." 1aecfbdg

898 

899 return metavar 1aecfbdg

900 

901 @overload 1aecfbdg

902 def get_default(self, ctx: Context, call: Literal[True] = True) -> Any | None: ... 1aecfbdg

903 

904 @overload 1aecfbdg

905 def get_default( 1aecfbdg

906 self, ctx: Context, call: bool = ... 1acbd

907 ) -> Any | Callable[[], Any] | None: ... 1acbd

908 

909 def get_default( 1aecfbdg

910 self, ctx: Context, call: bool = True 

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

912 """Get the default for the parameter""" 

913 value = ctx.lookup_default(self.name, call=False) # type: ignore 1aecfbdg

914 

915 if value is None: 1aecfbdg

916 value = self.default 1aecfbdg

917 

918 if call and callable(value): 1aecfbdg

919 value = value() 1aecfbdg

920 

921 return value 1aecfbdg

922 

923 @abstractmethod 1aecfbdg

924 def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: 1aecfbdg

925 pass # pragma: no cover 

926 

927 def consume_value( 1aecfbdg

928 self, ctx: Context, opts: Mapping[str, Any] 

929 ) -> tuple[Any, ParameterSource]: 

930 value = opts.get(self.name) # type: ignore # ty: ignore[unused-ignore-comment] 1aecfbdg

931 source = ParameterSource.COMMANDLINE 1aecfbdg

932 

933 if value is None: 1aecfbdg

934 value = self.value_from_envvar(ctx) 1aecfbdg

935 source = ParameterSource.ENVIRONMENT 1aecfbdg

936 

937 if value is None: 1aecfbdg

938 value = ctx.lookup_default(self.name) # type: ignore 1aecfbdg

939 source = ParameterSource.DEFAULT_MAP 1aecfbdg

940 

941 if value is None: 1aecfbdg

942 value = self.get_default(ctx) 1aecfbdg

943 source = ParameterSource.DEFAULT 1aecfbdg

944 

945 return value, source 1aecfbdg

946 

947 def type_cast_value(self, ctx: Context, value: Any) -> Any: 1aecfbdg

948 """Convert and validate a value against the parameter's 

949 `type`, `multiple`, and `nargs`. 

950 """ 

951 if value is None: 1aecfbdg

952 return () if self.multiple or self.nargs == -1 else None 1aecfbdg

953 

954 def check_iter(value: Any) -> Iterator[Any]: 1aecfbdg

955 if isinstance(value, str): 1aecfbdg

956 raise BadParameter("Value must be an iterable.", ctx=ctx, param=self) 1aecfbdg

957 else: 

958 return iter(value) 1aecfbdg

959 

960 # Define the conversion function based on nargs and type. 

961 if self.nargs == 1 or self.type.is_composite: 1aecfbdg

962 

963 def convert(value: Any) -> Any: 1aecfbdg

964 return self.type(value, param=self, ctx=ctx) 1aecfbdg

965 

966 elif self.nargs == -1: 1aecfbdg

967 

968 def convert(value: Any) -> Any: # tuple[t.Any, ...] 1aecfbdg

969 return tuple(self.type(x, self, ctx) for x in check_iter(value)) 1aecfbdg

970 

971 # TODO: evaluate whether we need to keep this in Typer 

972 else: # nargs > 1 

973 

974 def convert(value: Any) -> Any: # tuple[t.Any, ...] 1aecfbdg

975 value = tuple(check_iter(value)) 1aecfbdg

976 

977 if len(value) != self.nargs: 1aecfbdg

978 raise BadParameter( 1aecfbdg

979 f"Takes {self.nargs} values but {len(value)} given.", 

980 ctx=ctx, 

981 param=self, 

982 ) 

983 

984 return tuple(self.type(x, self, ctx) for x in value) 1aecfbdg

985 

986 if self.multiple: 1aecfbdg

987 return tuple(convert(x) for x in check_iter(value)) 1aecfbdg

988 

989 return convert(value) 1aecfbdg

990 

991 @abstractmethod 1aecfbdg

992 def value_is_missing(self, value: Any) -> bool: 1aecfbdg

993 pass # pragma: no cover 

994 

995 def process_value(self, ctx: Context, value: Any) -> Any: 1aecfbdg

996 """Process the value of this parameter""" 

997 value = self.type_cast_value(ctx, value) 1aecfbdg

998 

999 if self.required and self.value_is_missing(value): 1aecfbdg

1000 raise MissingParameter(ctx=ctx, param=self) 1aecfbdg

1001 

1002 if self.callback is not None: 1aecfbdg

1003 value = self.callback(ctx, self, value) 1aecfbdg

1004 

1005 return value 1aecfbdg

1006 

1007 def resolve_envvar_value(self, ctx: Context) -> str | None: 1aecfbdg

1008 """Returns the value found in the environment variable(s) attached to this 

1009 parameter. 

1010 

1011 Environment variables values are `always returned as strings 

1012 <https://docs.python.org/3/library/os.html#os.environ>`_. 

1013 

1014 This method returns ``None`` if: 

1015 

1016 - the `envvar` property is not set on `Parameter`, 

1017 - the environment variable is not found in the environment, 

1018 - the variable is found in the environment but its value is empty (i.e. the 

1019 environment variable is present but has an empty string). 

1020 

1021 If `envvar` is setup with multiple environment variables, 

1022 then only the first non-empty value is returned. 

1023 """ 

1024 if self.envvar is None: 1aecfbdg

1025 return None 1aecfbdg

1026 

1027 if isinstance(self.envvar, str): 1aecfbdg

1028 rv = os.environ.get(self.envvar) 1aecfbdg

1029 

1030 if rv: 1aecfbdg

1031 return rv 1aecfbdg

1032 else: 

1033 for envvar in self.envvar: 1aecfbdg

1034 rv = os.environ.get(envvar) 1aecfbdg

1035 

1036 # Return the first non-empty value of the list of environment variables. 

1037 if rv: 1aecfbdg

1038 return rv 1aecfbdg

1039 # Else, absence of value is interpreted as an environment variable that 

1040 # is not set, so proceed to the next one. 

1041 

1042 return None 1aecfbdg

1043 

1044 def value_from_envvar(self, ctx: Context) -> str | Sequence[str] | None: 1aecfbdg

1045 """Process the raw environment variable string for this parameter. 

1046 

1047 Returns the string as-is or splits it into a sequence of strings if the 

1048 parameter is expecting multiple values (i.e. its `nargs` property is set 

1049 to a value other than ``1``). 

1050 """ 

1051 rv: Any | None = self.resolve_envvar_value(ctx) 1aecfbdg

1052 

1053 if rv is not None and self.nargs != 1: 1aecfbdg

1054 rv = self.type.split_envvar_value(rv) 1aecfbdg

1055 

1056 return rv 1aecfbdg

1057 

1058 def handle_parse_result( 1aecfbdg

1059 self, ctx: Context, opts: Mapping[str, Any], args: list[str] 

1060 ) -> tuple[Any, list[str]]: 

1061 """Process the value produced by the parser from user input. 

1062 

1063 Always process the value through the Parameter's `type`, wherever it 

1064 comes from. 

1065 

1066 If the parameter is deprecated, this method warn the user about it. But only if 

1067 the value has been explicitly set by the user (and as such, is not coming from 

1068 a default). 

1069 """ 

1070 with augment_usage_errors(ctx, param=self): 1aecfbdg

1071 value, source = self.consume_value(ctx, opts) 1aecfbdg

1072 

1073 ctx.set_parameter_source(self.name, source) # type: ignore 1aecfbdg

1074 

1075 # Process the value through the parameter's type. 

1076 try: 1aecfbdg

1077 value = self.process_value(ctx, value) 1aecfbdg

1078 except Exception: 1aecfbdg

1079 if not ctx.resilient_parsing: 1aecfbdg

1080 raise 1aecfbdg

1081 value = None 1aecfbdg

1082 

1083 if self.expose_value: 1aecfbdg

1084 ctx.params[self.name] = value # type: ignore 1aecfbdg

1085 

1086 return value, args 1aecfbdg

1087 

1088 @abstractmethod 1aecfbdg

1089 def get_help_record(self, ctx: Context) -> tuple[str, str] | None: 1aecfbdg

1090 pass # pragma: no cover 

1091 

1092 def get_usage_pieces(self, ctx: Context) -> list[str]: 1aecfbdg

1093 return [] 1aecfbdg

1094 

1095 def get_error_hint(self, ctx: Context) -> str: 1aecfbdg

1096 """Get a stringified version of the param for use in error messages to 

1097 indicate which param caused the error. 

1098 """ 

1099 hint_list = self.opts or [self.human_readable_name] 1aecfbdg

1100 return " / ".join(f"'{x}'" for x in hint_list) 1aecfbdg

1101 

1102 def shell_complete(self, ctx: Context, incomplete: str) -> list["CompletionItem"]: 1aecfbdg

1103 """Return a list of completions for the incomplete value. If a 

1104 ``shell_complete`` function was given during init, it is used. 

1105 Otherwise, the `type` `ParamType.shell_complete` function is used. 

1106 """ 

1107 if self._custom_shell_complete is not None: 1aecfbdg

1108 results = self._custom_shell_complete(ctx, self, incomplete) 1aecfbdg

1109 

1110 if results and isinstance(results[0], str): 1aecfbdg

1111 from .shell_completion import CompletionItem 1aecfbdg

1112 

1113 results = [CompletionItem(c) for c in results] 1aecfbdg

1114 

1115 return cast("list[CompletionItem]", results) 1aecfbdg

1116 

1117 return self.type.shell_complete(ctx, self, incomplete) 1aecfbdg