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
« 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)
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
35if TYPE_CHECKING: 1aecfbdg
36 from ..core import TyperOption
37 from .shell_completion import CompletionItem
39F = TypeVar("F", bound="Callable[..., Any]") 1aecfbdg
40V = TypeVar("V") 1aecfbdg
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
52 multi = cast(TyperGroup, ctx.command) 1aecfbdg
54 for name in multi.list_commands(ctx): 1aecfbdg
55 if name.startswith(incomplete): 1aecfbdg
56 command = multi.get_command(ctx, name) 1aecfbdg
58 if command is not None and not command.hidden: 1aecfbdg
59 yield name, command 1aecfbdg
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
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.
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.
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.
93 This behavior and its effect on callback evaluation is detailed at:
94 https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order
95 """
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
103 return not item.is_eager, idx 1aecfbdg
105 return sorted(declaration_order, key=sort_key) 1aecfbdg
108class ParameterSource(enum.Enum): 1aecfbdg
109 """This is an `Enum` that indicates the source of a
110 parameter's value.
111 """
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
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.
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.
134 A context can be used as context manager in which case it will call
135 `close` on teardown.
136 """
138 formatter_class: type[HelpFormatter] = HelpFormatter 1aecfbdg
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
171 if obj is None and parent is not None: 1aecfbdg
172 obj = parent.obj 1aecfbdg
174 self.obj: Any = obj 1aecfbdg
175 self._meta: dict[str, Any] = getattr(parent, "meta", {}) 1aecfbdg
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
186 self.default_map: MutableMapping[str, Any] | None = default_map 1aecfbdg
188 # This flag indicates if a subcommand is going to be executed.
189 self.invoked_subcommand: str | None = None 1aecfbdg
191 if terminal_width is None and parent is not None: 1aecfbdg
192 terminal_width = parent.terminal_width 1aecfbdg
194 # The width of the terminal (None is autodetection).
195 self.terminal_width: int | None = terminal_width 1aecfbdg
197 if max_content_width is None and parent is not None: 1aecfbdg
198 max_content_width = parent.max_content_width 1aecfbdg
200 self.max_content_width: int | None = max_content_width 1aecfbdg
202 if allow_extra_args is None: 1aecfbdg
203 allow_extra_args = command.allow_extra_args 1aecfbdg
205 self.allow_extra_args = allow_extra_args 1aecfbdg
207 if allow_interspersed_args is None: 1aecfbdg
208 allow_interspersed_args = command.allow_interspersed_args 1aecfbdg
210 self.allow_interspersed_args: bool = allow_interspersed_args 1aecfbdg
212 if ignore_unknown_options is None: 1aecfbdg
213 ignore_unknown_options = command.ignore_unknown_options 1aecfbdg
215 self.ignore_unknown_options: bool = ignore_unknown_options 1aecfbdg
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
223 self.help_option_names: list[str] = help_option_names 1aecfbdg
225 if token_normalize_func is None and parent is not None: 1aecfbdg
226 token_normalize_func = parent.token_normalize_func 1aecfbdg
228 # An optional normalization function for tokens. (options, choices, commands etc.)
229 self.token_normalize_func: Callable[[str], str] | None = token_normalize_func 1aecfbdg
231 # Indicates if resilient parsing is enabled.
232 self.resilient_parsing: bool = resilient_parsing 1aecfbdg
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
249 if auto_envvar_prefix is not None: 1aecfbdg
250 auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") 1aecfbdg
252 self.auto_envvar_prefix: str | None = auto_envvar_prefix 1aecfbdg
254 if color is None and parent is not None: 1aecfbdg
255 color = parent.color 1aecfbdg
257 # Controls if styling output is wanted or not.
258 self.color: bool | None = color 1aecfbdg
260 if show_default is None and parent is not None: 1aecfbdg
261 show_default = parent.show_default 1aecfbdg
263 # Show option default values when formatting help text.
264 self.show_default: bool | None = show_default 1aecfbdg
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
271 def __enter__(self) -> "Context": 1aecfbdg
272 self._depth += 1 1aecfbdg
273 push_context(self) 1aecfbdg
274 return self 1aecfbdg
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
288 return exit_result 1aecfbdg
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.
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
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.
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
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 )
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.
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.
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
349 def call_on_close(self, f: Callable[..., Any]) -> Callable[..., Any]: 1aecfbdg
350 """Register a function to be called when the context tears down.
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
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
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
379 return exit_result 1aecfbdg
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
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
397 rv = f"{' '.join(parent_command_path)} {rv}" 1aecfbdg
398 return rv.lstrip() 1aecfbdg
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
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
411 while node is not None: 1aecfbdg
412 if isinstance(node.obj, object_type): 1aecfbdg
413 return node.obj 1aecfbdg
415 node = node.parent 1aecfbdg
417 return None 1aecfbdg
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
428 @overload 1aecfbdg
429 def lookup_default(self, name: str, call: Literal[True] = True) -> Any | None: ... 1aecfbdg
431 @overload 1aecfbdg
432 def lookup_default( 1aecfbdg
433 self, name: str, call: Literal[False] = ... 1acbd
434 ) -> Any | Callable[[], Any] | None: ... 1acbd
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
441 if call and callable(value): 1aecfbdg
442 return value() 1aecfbdg
444 return value 1aecfbdg
446 return None 1aecfbdg
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
454 def abort(self) -> NoReturn: 1aecfbdg
455 """Aborts the script."""
456 raise Abort() 1aecfbdg
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
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
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
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:
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
488 with augment_usage_errors(self): 1aecfbdg
489 with ctx: 1aecfbdg
490 return callback(*args, **kwargs) 1aecfbdg
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
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.
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
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 """
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
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
538 if context_settings is None: 1aecfbdg
539 context_settings = {} 1aecfbdg
541 self.context_settings: MutableMapping[str, Any] = context_settings 1aecfbdg
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
555 def __repr__(self) -> str: 1aecfbdg
556 return f"<{self.__class__.__name__} {self.name}>" 1aecfbdg
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
564 def get_params(self, ctx: Context) -> list["Parameter"]: 1aecfbdg
565 params = self.params 1aecfbdg
566 help_option = self.get_help_option(ctx) 1aecfbdg
568 if help_option is not None: 1aecfbdg
569 params = [*params, help_option] 1aecfbdg
571 return params 1aecfbdg
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
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
584 for param in self.get_params(ctx): 1aecfbdg
585 rv.extend(param.get_usage_pieces(ctx)) 1aecfbdg
587 return rv 1aecfbdg
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
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
601 if not help_option_names or not self.add_help_option: 1aecfbdg
602 return None 1aecfbdg
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
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
616 return self._help_option 1aecfbdg
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
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
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
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
650 return text.strip() 1aecfbdg
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
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
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
675 if text: 1aecfbdg
676 formatter.write_paragraph() 1aecfbdg
678 with formatter.indentation(): 1aecfbdg
679 formatter.write_text(text) 1aecfbdg
681 @abstractmethod 1aecfbdg
682 def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: 1aecfbdg
683 pass # pragma: no cover
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
691 with formatter.indentation(): 1aecfbdg
692 formatter.write_text(epilog) 1aecfbdg
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.
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
712 ctx = self.context_class(self, info_name=info_name, parent=parent, **extra) 1aecfbdg
714 with ctx.scope(cleanup=False): 1aecfbdg
715 self.parse_args(ctx, args) 1aecfbdg
716 return ctx 1aecfbdg
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
722 parser = self.make_parser(ctx) 1aecfbdg
723 opts, args, param_order = parser.parse_args(args=args) 1aecfbdg
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
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
732 ctx.args = args 1aecfbdg
733 ctx._opt_prefixes.update(parser._opt_prefixes) 1aecfbdg
734 return args 1aecfbdg
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
747 if self.callback is not None: 1aecfbdg
748 return ctx.invoke(self.callback, **ctx.params) 1aecfbdg
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.
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
760 results: list[CompletionItem] = [] 1aecfbdg
762 if incomplete and not incomplete[0].isalnum(): 1aecfbdg
763 # avoid circular imports
764 from ..core import TyperOption 1aecfbdg
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
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 )
784 return results 1aecfbdg
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
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
807 def __call__(self, *args: Any, **kwargs: Any) -> Any: 1aecfbdg
808 """Alias for self.main"""
809 return self.main(*args, **kwargs) 1aecfbdg
812class Parameter(ABC): 1aecfbdg
813 r"""A parameter to a command comes in two versions: they are either
814 `Option`\s or `Argument`\s.
816 Some settings are supported by both options and arguments.
817 """
819 param_type_name = "parameter" 1aecfbdg
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
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
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
866 def __repr__(self) -> str: 1aecfbdg
867 return f"<{self.__class__.__name__} {self.name}>" 1acbd
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
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
883 def make_metavar(self, ctx: Context) -> str: 1aecfbdg
884 if self.metavar is not None: 1aecfbdg
885 return self.metavar 1aecfbdg
887 metavar = self.type.get_metavar(param=self, ctx=ctx) 1aecfbdg
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
896 if self.nargs != 1: 1aecfbdg
897 metavar += "..." 1aecfbdg
899 return metavar 1aecfbdg
901 @overload 1aecfbdg
902 def get_default(self, ctx: Context, call: Literal[True] = True) -> Any | None: ... 1aecfbdg
904 @overload 1aecfbdg
905 def get_default( 1aecfbdg
906 self, ctx: Context, call: bool = ... 1acbd
907 ) -> Any | Callable[[], Any] | None: ... 1acbd
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
915 if value is None: 1aecfbdg
916 value = self.default 1aecfbdg
918 if call and callable(value): 1aecfbdg
919 value = value() 1aecfbdg
921 return value 1aecfbdg
923 @abstractmethod 1aecfbdg
924 def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: 1aecfbdg
925 pass # pragma: no cover
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
933 if value is None: 1aecfbdg
934 value = self.value_from_envvar(ctx) 1aecfbdg
935 source = ParameterSource.ENVIRONMENT 1aecfbdg
937 if value is None: 1aecfbdg
938 value = ctx.lookup_default(self.name) # type: ignore 1aecfbdg
939 source = ParameterSource.DEFAULT_MAP 1aecfbdg
941 if value is None: 1aecfbdg
942 value = self.get_default(ctx) 1aecfbdg
943 source = ParameterSource.DEFAULT 1aecfbdg
945 return value, source 1aecfbdg
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
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
960 # Define the conversion function based on nargs and type.
961 if self.nargs == 1 or self.type.is_composite: 1aecfbdg
963 def convert(value: Any) -> Any: 1aecfbdg
964 return self.type(value, param=self, ctx=ctx) 1aecfbdg
966 elif self.nargs == -1: 1aecfbdg
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
971 # TODO: evaluate whether we need to keep this in Typer
972 else: # nargs > 1
974 def convert(value: Any) -> Any: # tuple[t.Any, ...] 1aecfbdg
975 value = tuple(check_iter(value)) 1aecfbdg
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 )
984 return tuple(self.type(x, self, ctx) for x in value) 1aecfbdg
986 if self.multiple: 1aecfbdg
987 return tuple(convert(x) for x in check_iter(value)) 1aecfbdg
989 return convert(value) 1aecfbdg
991 @abstractmethod 1aecfbdg
992 def value_is_missing(self, value: Any) -> bool: 1aecfbdg
993 pass # pragma: no cover
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
999 if self.required and self.value_is_missing(value): 1aecfbdg
1000 raise MissingParameter(ctx=ctx, param=self) 1aecfbdg
1002 if self.callback is not None: 1aecfbdg
1003 value = self.callback(ctx, self, value) 1aecfbdg
1005 return value 1aecfbdg
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.
1011 Environment variables values are `always returned as strings
1012 <https://docs.python.org/3/library/os.html#os.environ>`_.
1014 This method returns ``None`` if:
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).
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
1027 if isinstance(self.envvar, str): 1aecfbdg
1028 rv = os.environ.get(self.envvar) 1aecfbdg
1030 if rv: 1aecfbdg
1031 return rv 1aecfbdg
1032 else:
1033 for envvar in self.envvar: 1aecfbdg
1034 rv = os.environ.get(envvar) 1aecfbdg
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.
1042 return None 1aecfbdg
1044 def value_from_envvar(self, ctx: Context) -> str | Sequence[str] | None: 1aecfbdg
1045 """Process the raw environment variable string for this parameter.
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
1053 if rv is not None and self.nargs != 1: 1aecfbdg
1054 rv = self.type.split_envvar_value(rv) 1aecfbdg
1056 return rv 1aecfbdg
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.
1063 Always process the value through the Parameter's `type`, wherever it
1064 comes from.
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
1073 ctx.set_parameter_source(self.name, source) # type: ignore 1aecfbdg
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
1083 if self.expose_value: 1aecfbdg
1084 ctx.params[self.name] = value # type: ignore 1aecfbdg
1086 return value, args 1aecfbdg
1088 @abstractmethod 1aecfbdg
1089 def get_help_record(self, ctx: Context) -> tuple[str, str] | None: 1aecfbdg
1090 pass # pragma: no cover
1092 def get_usage_pieces(self, ctx: Context) -> list[str]: 1aecfbdg
1093 return [] 1aecfbdg
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
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
1110 if results and isinstance(results[0], str): 1aecfbdg
1111 from .shell_completion import CompletionItem 1aecfbdg
1113 results = [CompletionItem(c) for c in results] 1aecfbdg
1115 return cast("list[CompletionItem]", results) 1aecfbdg
1117 return self.type.shell_complete(ctx, self, incomplete) 1aecfbdg