Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
|
|
@ -7,6 +7,8 @@ import inspect
|
|||
import os
|
||||
import sys
|
||||
import typing as t
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from collections import abc
|
||||
from collections import Counter
|
||||
from contextlib import AbstractContextManager
|
||||
|
|
@ -27,6 +29,7 @@ from .exceptions import ClickException
|
|||
from .exceptions import Exit
|
||||
from .exceptions import MissingParameter
|
||||
from .exceptions import NoArgsIsHelpError
|
||||
from .exceptions import NoSuchCommand
|
||||
from .exceptions import UsageError
|
||||
from .formatting import HelpFormatter
|
||||
from .formatting import join_options
|
||||
|
|
@ -90,6 +93,23 @@ def _check_nested_chain(
|
|||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def _format_deprecated_label(deprecated: bool | str) -> str:
|
||||
"""Return the parenthesized deprecation label shown in help text."""
|
||||
label = _("deprecated").upper()
|
||||
if isinstance(deprecated, str):
|
||||
return f"({label}: {deprecated})"
|
||||
return f"({label})"
|
||||
|
||||
|
||||
def _format_deprecated_suffix(deprecated: bool | str) -> str:
|
||||
"""Return the trailing reason for a ``DeprecationWarning`` message,
|
||||
prefixed with a space, or an empty string when no reason was given.
|
||||
"""
|
||||
if isinstance(deprecated, str):
|
||||
return f" {deprecated}"
|
||||
return ""
|
||||
|
||||
|
||||
def batch(iterable: cabc.Iterable[V], batch_size: int) -> list[tuple[V, ...]]:
|
||||
return list(zip(*repeat(iter(iterable), batch_size), strict=False))
|
||||
|
||||
|
|
@ -140,13 +160,26 @@ def iter_params_for_processing(
|
|||
return sorted(declaration_order, key=sort_key)
|
||||
|
||||
|
||||
class ParameterSource(enum.Enum):
|
||||
"""This is an :class:`~enum.Enum` that indicates the source of a
|
||||
class ParameterSource(enum.IntEnum):
|
||||
"""This is an :class:`~enum.IntEnum` that indicates the source of a
|
||||
parameter's value.
|
||||
|
||||
Use :meth:`click.Context.get_parameter_source` to get the
|
||||
source for a parameter by name.
|
||||
|
||||
Members are ordered from most explicit to least explicit source.
|
||||
This allows comparison to check if a value was explicitly provided:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
source = ctx.get_parameter_source("port")
|
||||
if source < click.ParameterSource.DEFAULT_MAP:
|
||||
... # value was explicitly set
|
||||
|
||||
.. versionchanged:: 8.3.3
|
||||
Use :class:`~enum.IntEnum` and reorder members from most to
|
||||
least explicit. Supports comparison operators.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Use :class:`~enum.Enum` and drop the ``validate`` method.
|
||||
|
||||
|
|
@ -154,16 +187,16 @@ class ParameterSource(enum.Enum):
|
|||
Added the ``PROMPT`` value.
|
||||
"""
|
||||
|
||||
PROMPT = enum.auto()
|
||||
"""Used a prompt to confirm a default or provide a value."""
|
||||
COMMANDLINE = enum.auto()
|
||||
"""The value was provided by the command line args."""
|
||||
ENVIRONMENT = enum.auto()
|
||||
"""The value was provided with an environment variable."""
|
||||
DEFAULT = enum.auto()
|
||||
"""Used the default specified by the parameter."""
|
||||
DEFAULT_MAP = enum.auto()
|
||||
"""Used a default provided by :attr:`Context.default_map`."""
|
||||
PROMPT = enum.auto()
|
||||
"""Used a prompt to confirm a default or provide a value."""
|
||||
DEFAULT = enum.auto()
|
||||
"""Used the default specified by the parameter."""
|
||||
|
||||
|
||||
class Context:
|
||||
|
|
@ -183,7 +216,7 @@ class Context:
|
|||
:param info_name: the info name for this invocation. Generally this
|
||||
is the most descriptive name for the script or
|
||||
command. For the toplevel script it is usually
|
||||
the name of the script, for commands below it it's
|
||||
the name of the script, for commands below that it's
|
||||
the name of the script.
|
||||
:param obj: an arbitrary object of user data.
|
||||
:param auto_envvar_prefix: the prefix to use for automatic environment
|
||||
|
|
@ -438,6 +471,12 @@ class Context:
|
|||
self._close_callbacks: list[t.Callable[[], t.Any]] = []
|
||||
self._depth = 0
|
||||
self._parameter_source: dict[str, ParameterSource] = {}
|
||||
# Tracks whether the option that currently owns each parameter slot in
|
||||
# :attr:`params` had its ``default`` set explicitly by the user. Used
|
||||
# to tie-break feature-switch groups where multiple options share a
|
||||
# parameter name and both fall back to their default value.
|
||||
# Refs: https://github.com/pallets/click/issues/3403
|
||||
self._param_default_explicit: dict[str, bool] = {}
|
||||
self._exit_stack = ExitStack()
|
||||
|
||||
@property
|
||||
|
|
@ -685,6 +724,20 @@ class Context:
|
|||
self.obj = rv = object_type()
|
||||
return rv
|
||||
|
||||
def _default_map_has(self, name: str | None) -> bool:
|
||||
"""Check if :attr:`default_map` contains a real value for ``name``.
|
||||
|
||||
Returns ``False`` when the key is absent, the map is ``None``,
|
||||
``name`` is ``None``, or the stored value is the internal
|
||||
:data:`UNSET` sentinel.
|
||||
"""
|
||||
return (
|
||||
name is not None
|
||||
and self.default_map is not None
|
||||
and name in self.default_map
|
||||
and self.default_map[name] is not UNSET
|
||||
)
|
||||
|
||||
@t.overload
|
||||
def lookup_default(
|
||||
self, name: str, call: t.Literal[True] = True
|
||||
|
|
@ -705,15 +758,17 @@ class Context:
|
|||
.. versionchanged:: 8.0
|
||||
Added the ``call`` parameter.
|
||||
"""
|
||||
if self.default_map is not None:
|
||||
value = self.default_map.get(name, UNSET)
|
||||
if not self._default_map_has(name):
|
||||
return None
|
||||
|
||||
if call and callable(value):
|
||||
return value()
|
||||
# Assert to make the type checker happy.
|
||||
assert self.default_map is not None
|
||||
value = self.default_map[name]
|
||||
|
||||
return value
|
||||
if call and callable(value):
|
||||
return value()
|
||||
|
||||
return UNSET
|
||||
return value
|
||||
|
||||
def fail(self, message: str) -> t.NoReturn:
|
||||
"""Aborts the execution of the program with a specific error
|
||||
|
|
@ -809,9 +864,7 @@ class Context:
|
|||
# https://github.com/pallets/click/pull/3068
|
||||
if default_value is UNSET:
|
||||
default_value = None
|
||||
kwargs[param.name] = param.type_cast_value( # type: ignore
|
||||
ctx, default_value
|
||||
)
|
||||
kwargs[param.name] = param.type_cast_value(ctx, default_value)
|
||||
|
||||
# Track all kwargs as params, so that forward() will pass
|
||||
# them on in subsequent calls.
|
||||
|
|
@ -1066,7 +1119,7 @@ class Command:
|
|||
|
||||
# Cache the help option object in private _help_option attribute to
|
||||
# avoid creating it multiple times. Not doing this will break the
|
||||
# callback odering by iter_params_for_processing(), which relies on
|
||||
# callback ordering by iter_params_for_processing(), which relies on
|
||||
# object comparison.
|
||||
if self._help_option is None:
|
||||
# Avoid circular import.
|
||||
|
|
@ -1106,14 +1159,7 @@ class Command:
|
|||
text = ""
|
||||
|
||||
if self.deprecated:
|
||||
deprecated_message = (
|
||||
f"(DEPRECATED: {self.deprecated})"
|
||||
if isinstance(self.deprecated, str)
|
||||
else "(DEPRECATED)"
|
||||
)
|
||||
text = _("{text} {deprecated_message}").format(
|
||||
text=text, deprecated_message=deprecated_message
|
||||
)
|
||||
text = f"{_(text)} {_format_deprecated_label(self.deprecated)}"
|
||||
|
||||
return text.strip()
|
||||
|
||||
|
|
@ -1143,14 +1189,7 @@ class Command:
|
|||
text = ""
|
||||
|
||||
if self.deprecated:
|
||||
deprecated_message = (
|
||||
f"(DEPRECATED: {self.deprecated})"
|
||||
if isinstance(self.deprecated, str)
|
||||
else "(DEPRECATED)"
|
||||
)
|
||||
text = _("{text} {deprecated_message}").format(
|
||||
text=text, deprecated_message=deprecated_message
|
||||
)
|
||||
text = f"{_(text)} {_format_deprecated_label(self.deprecated)}"
|
||||
|
||||
if text:
|
||||
formatter.write_paragraph()
|
||||
|
|
@ -1257,12 +1296,12 @@ class Command:
|
|||
in the right way.
|
||||
"""
|
||||
if self.deprecated:
|
||||
extra_message = (
|
||||
f" {self.deprecated}" if isinstance(self.deprecated, str) else ""
|
||||
)
|
||||
message = _(
|
||||
"DeprecationWarning: The command {name!r} is deprecated.{extra_message}"
|
||||
).format(name=self.name, extra_message=extra_message)
|
||||
).format(
|
||||
name=self.name,
|
||||
extra_message=_format_deprecated_suffix(self.deprecated),
|
||||
)
|
||||
echo(style(message, fg="red"), err=True)
|
||||
|
||||
if self.callback is not None:
|
||||
|
|
@ -1291,7 +1330,7 @@ class Command:
|
|||
or param.hidden
|
||||
or (
|
||||
not param.multiple
|
||||
and ctx.get_parameter_source(param.name) # type: ignore
|
||||
and ctx.get_parameter_source(param.name)
|
||||
is ParameterSource.COMMANDLINE
|
||||
)
|
||||
):
|
||||
|
|
@ -1908,7 +1947,6 @@ class Group(Command):
|
|||
self, ctx: Context, args: list[str]
|
||||
) -> tuple[str | None, Command | None, list[str]]:
|
||||
cmd_name = make_str(args[0])
|
||||
original_cmd_name = cmd_name
|
||||
|
||||
# Get the command
|
||||
cmd = self.get_command(ctx, cmd_name)
|
||||
|
|
@ -1928,7 +1966,7 @@ class Group(Command):
|
|||
if cmd is None and not ctx.resilient_parsing:
|
||||
if _split_opt(cmd_name)[0]:
|
||||
self.parse_args(ctx, args)
|
||||
ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name))
|
||||
raise NoSuchCommand(cmd_name, possibilities=self.commands, ctx=ctx)
|
||||
return cmd_name if cmd else None, cmd, args[1:]
|
||||
|
||||
def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]:
|
||||
|
|
@ -2024,7 +2062,7 @@ def _check_iter(value: t.Any) -> cabc.Iterator[t.Any]:
|
|||
return iter(value)
|
||||
|
||||
|
||||
class Parameter:
|
||||
class Parameter(ABC):
|
||||
r"""A parameter to a command comes in two versions: they are either
|
||||
:class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
|
||||
not supported by design as some of the internals for parsing are
|
||||
|
|
@ -2120,7 +2158,7 @@ class Parameter:
|
|||
def __init__(
|
||||
self,
|
||||
param_decls: cabc.Sequence[str] | None = None,
|
||||
type: types.ParamType | t.Any | None = None,
|
||||
type: types.ParamType[t.Any] | t.Any | None = None,
|
||||
required: bool = False,
|
||||
# XXX The default historically embed two concepts:
|
||||
# - the declaration of a Parameter object carrying the default (handy to
|
||||
|
|
@ -2146,13 +2184,13 @@ class Parameter:
|
|||
| None = None,
|
||||
deprecated: bool | str = False,
|
||||
) -> None:
|
||||
self.name: str | None
|
||||
self.name: str
|
||||
self.opts: list[str]
|
||||
self.secondary_opts: list[str]
|
||||
self.name, self.opts, self.secondary_opts = self._parse_decls(
|
||||
param_decls or (), expose_value
|
||||
)
|
||||
self.type: types.ParamType = types.convert_type(type, default)
|
||||
self.type: types.ParamType[t.Any] = types.convert_type(type, default)
|
||||
|
||||
# Default nargs to what the type tells us if we have that
|
||||
# information available.
|
||||
|
|
@ -2168,6 +2206,12 @@ class Parameter:
|
|||
self.multiple = multiple
|
||||
self.expose_value = expose_value
|
||||
self.default: t.Any | t.Callable[[], t.Any] | None = default
|
||||
# Whether the user passed ``default`` explicitly to the constructor.
|
||||
# Captured before any auto-derived default (like ``False`` for boolean
|
||||
# flags in :class:`Option`) replaces the :data:`UNSET` sentinel, so it
|
||||
# remains ``False`` when the default was inferred rather than chosen.
|
||||
# Refs: https://github.com/pallets/click/issues/3403
|
||||
self._default_explicit: bool = default is not UNSET
|
||||
self.is_eager = is_eager
|
||||
self.metavar = metavar
|
||||
self.envvar = envvar
|
||||
|
|
@ -2219,17 +2263,17 @@ class Parameter:
|
|||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__} {self.name}>"
|
||||
|
||||
@abstractmethod
|
||||
def _parse_decls(
|
||||
self, decls: cabc.Sequence[str], expose_value: bool
|
||||
) -> tuple[str | None, list[str], list[str]]:
|
||||
raise NotImplementedError()
|
||||
) -> tuple[str, list[str], list[str]]: ...
|
||||
|
||||
@property
|
||||
def human_readable_name(self) -> str:
|
||||
"""Returns the human readable name of this parameter. This is the
|
||||
same as the name for options, but the metavar for arguments.
|
||||
"""
|
||||
return self.name # type: ignore
|
||||
return self.name
|
||||
|
||||
def make_metavar(self, ctx: Context) -> str:
|
||||
if self.metavar is not None:
|
||||
|
|
@ -2278,9 +2322,9 @@ class Parameter:
|
|||
.. versionchanged:: 8.0
|
||||
Added the ``call`` parameter.
|
||||
"""
|
||||
value = ctx.lookup_default(self.name, call=False) # type: ignore
|
||||
value = ctx.lookup_default(self.name, call=False)
|
||||
|
||||
if value is UNSET:
|
||||
if value is None and not ctx._default_map_has(self.name):
|
||||
value = self.default
|
||||
|
||||
if call and callable(value):
|
||||
|
|
@ -2288,8 +2332,8 @@ class Parameter:
|
|||
|
||||
return value
|
||||
|
||||
def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None:
|
||||
raise NotImplementedError()
|
||||
@abstractmethod
|
||||
def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: ...
|
||||
|
||||
def consume_value(
|
||||
self, ctx: Context, opts: cabc.Mapping[str, t.Any]
|
||||
|
|
@ -2305,7 +2349,7 @@ class Parameter:
|
|||
:meta private:
|
||||
"""
|
||||
# Collect from the parse the value passed by the user to the CLI.
|
||||
value = opts.get(self.name, UNSET) # type: ignore
|
||||
value = opts.get(self.name, UNSET)
|
||||
# If the value is set, it means it was sourced from the command line by the
|
||||
# parser, otherwise it left unset by default.
|
||||
source = (
|
||||
|
|
@ -2321,11 +2365,16 @@ class Parameter:
|
|||
source = ParameterSource.ENVIRONMENT
|
||||
|
||||
if value is UNSET:
|
||||
default_map_value = ctx.lookup_default(self.name) # type: ignore
|
||||
if default_map_value is not UNSET:
|
||||
default_map_value = ctx.lookup_default(self.name)
|
||||
if default_map_value is not None or ctx._default_map_has(self.name):
|
||||
value = default_map_value
|
||||
source = ParameterSource.DEFAULT_MAP
|
||||
|
||||
# A string from default_map must be split for multi-value
|
||||
# parameters, matching value_from_envvar behavior.
|
||||
if isinstance(value, str) and self.nargs != 1:
|
||||
value = self.type.split_envvar_value(value)
|
||||
|
||||
if value is UNSET:
|
||||
default_value = self.get_default(ctx)
|
||||
if default_value is not UNSET:
|
||||
|
|
@ -2549,27 +2598,35 @@ class Parameter:
|
|||
|
||||
:meta private:
|
||||
"""
|
||||
# Capture the slot's existing state before we mutate
|
||||
# ``_parameter_source`` so the write decision below can compare our
|
||||
# incoming source against the source of the option that already wrote
|
||||
# the slot (if any).
|
||||
existing_value = ctx.params.get(self.name, UNSET)
|
||||
existing_source = ctx.get_parameter_source(self.name)
|
||||
existing_default_explicit = ctx._param_default_explicit.get(self.name, False)
|
||||
|
||||
with augment_usage_errors(ctx, param=self):
|
||||
value, source = self.consume_value(ctx, opts)
|
||||
|
||||
ctx.set_parameter_source(self.name, source) # type: ignore
|
||||
# Record the source before processing so eager callbacks and type
|
||||
# conversion can inspect it. Restored after arbitration if this
|
||||
# option loses a feature-switch group.
|
||||
ctx.set_parameter_source(self.name, source)
|
||||
|
||||
# Display a deprecation warning if necessary.
|
||||
if (
|
||||
self.deprecated
|
||||
and value is not UNSET
|
||||
and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
|
||||
and source < ParameterSource.DEFAULT_MAP
|
||||
):
|
||||
extra_message = (
|
||||
f" {self.deprecated}" if isinstance(self.deprecated, str) else ""
|
||||
)
|
||||
message = _(
|
||||
"DeprecationWarning: The {param_type} {name!r} is deprecated."
|
||||
"{extra_message}"
|
||||
).format(
|
||||
param_type=self.param_type_name,
|
||||
name=self.human_readable_name,
|
||||
extra_message=extra_message,
|
||||
extra_message=_format_deprecated_suffix(self.deprecated),
|
||||
)
|
||||
echo(style(message, fg="red"), err=True)
|
||||
|
||||
|
|
@ -2584,32 +2641,49 @@ class Parameter:
|
|||
# to UNSET, which will be interpreted as a missing value.
|
||||
value = UNSET
|
||||
|
||||
# Add parameter's value to the context.
|
||||
if (
|
||||
self.expose_value
|
||||
# We skip adding the value if it was previously set by another parameter
|
||||
# targeting the same variable name. This prevents parameters competing for
|
||||
# the same name to override each other.
|
||||
and (self.name not in ctx.params or ctx.params[self.name] is UNSET)
|
||||
):
|
||||
# Click is logically enforcing that the name is None if the parameter is
|
||||
# not to be exposed. We still assert it here to please the type checker.
|
||||
assert self.name is not None, (
|
||||
f"{self!r} parameter's name should not be None when exposing value."
|
||||
)
|
||||
ctx.params[self.name] = value
|
||||
# Arbitrate the slot when several parameters target the same variable
|
||||
# name (feature-switch groups). See: https://github.com/pallets/click/issues/3403
|
||||
slot_empty = existing_value is UNSET
|
||||
more_explicit = existing_source is not None and source < existing_source
|
||||
same_source = existing_source is not None and source == existing_source
|
||||
auto_would_downgrade_explicit = (
|
||||
same_source
|
||||
and source == ParameterSource.DEFAULT
|
||||
and existing_default_explicit
|
||||
and not self._default_explicit
|
||||
)
|
||||
is_winner = (
|
||||
slot_empty
|
||||
or more_explicit
|
||||
or (same_source and not auto_would_downgrade_explicit)
|
||||
)
|
||||
|
||||
if is_winner:
|
||||
if self.expose_value:
|
||||
ctx.params[self.name] = value
|
||||
ctx._param_default_explicit[self.name] = self._default_explicit
|
||||
elif existing_source is not None:
|
||||
# Lost arbitration; restore the winning option's source.
|
||||
ctx.set_parameter_source(self.name, existing_source)
|
||||
# else: ctx.params[self.name] was populated by code that bypassed
|
||||
# handle_parse_result (from another option's callback for example). Keep
|
||||
# the provisional source recorded before process_value so downstream
|
||||
# lookups don't return ``None``.
|
||||
|
||||
return value, args
|
||||
|
||||
def get_help_record(self, ctx: Context) -> tuple[str, str] | None:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_usage_pieces(self, ctx: Context) -> list[str]:
|
||||
return []
|
||||
|
||||
def get_error_hint(self, ctx: Context) -> str:
|
||||
def get_error_hint(self, ctx: Context | None) -> str:
|
||||
"""Get a stringified version of the param for use in error messages to
|
||||
indicate which param caused the error.
|
||||
|
||||
.. versionchanged:: 8.4.0
|
||||
``ctx`` can be ``None``.
|
||||
"""
|
||||
hint_list = self.opts or [self.human_readable_name]
|
||||
return " / ".join(f"'{x}'" for x in hint_list)
|
||||
|
|
@ -2618,7 +2692,7 @@ class Parameter:
|
|||
"""Return a list of completions for the incomplete value. If a
|
||||
``shell_complete`` function was given during init, it is used.
|
||||
Otherwise, the :attr:`type`
|
||||
:meth:`~click.types.ParamType.shell_complete` function is used.
|
||||
:meth:`~click.types.ParamType[t.Any].shell_complete` function is used.
|
||||
|
||||
:param ctx: Invocation context for this command.
|
||||
:param incomplete: Value being completed. May be empty.
|
||||
|
|
@ -2684,6 +2758,11 @@ class Option(Parameter):
|
|||
:param hidden: hide this option from help outputs.
|
||||
:param attrs: Other command arguments described in :class:`Parameter`.
|
||||
|
||||
.. versionchanged:: 8.4.0
|
||||
Non-basic ``flag_value`` types (not ``str``, ``int``, ``float``, or
|
||||
``bool``) are passed through unchanged instead of being stringified.
|
||||
Previously, ``type=click.UNPROCESSED`` was required to preserve them.
|
||||
|
||||
.. versionchanged:: 8.2
|
||||
``envvar`` used with ``flag_value`` will always use the ``flag_value``,
|
||||
previously it would use the value of the environment variable.
|
||||
|
|
@ -2701,7 +2780,8 @@ class Option(Parameter):
|
|||
default value is ``False``.
|
||||
|
||||
.. versionchanged:: 8.0.1
|
||||
``type`` is detected from ``flag_value`` if given.
|
||||
``type`` is detected from ``flag_value`` if given, for basic Python
|
||||
types (``str``, ``int``, ``float``, ``bool``).
|
||||
"""
|
||||
|
||||
param_type_name = "option"
|
||||
|
|
@ -2719,7 +2799,7 @@ class Option(Parameter):
|
|||
multiple: bool = False,
|
||||
count: bool = False,
|
||||
allow_from_autoenv: bool = True,
|
||||
type: types.ParamType | t.Any | None = None,
|
||||
type: types.ParamType[t.Any] | t.Any | None = None,
|
||||
help: str | None = None,
|
||||
hidden: bool = False,
|
||||
show_choices: bool = True,
|
||||
|
|
@ -2735,7 +2815,7 @@ class Option(Parameter):
|
|||
)
|
||||
|
||||
if prompt is True:
|
||||
if self.name is None:
|
||||
if not self.name:
|
||||
raise TypeError("'name' is required with 'prompt=True'.")
|
||||
|
||||
prompt_text: str | None = self.name.replace("_", " ").capitalize()
|
||||
|
|
@ -2745,12 +2825,8 @@ class Option(Parameter):
|
|||
prompt_text = prompt
|
||||
|
||||
if deprecated:
|
||||
deprecated_message = (
|
||||
f"(DEPRECATED: {deprecated})"
|
||||
if isinstance(deprecated, str)
|
||||
else "(DEPRECATED)"
|
||||
)
|
||||
help = help + deprecated_message if help is not None else deprecated_message
|
||||
label = _format_deprecated_label(deprecated)
|
||||
help = f"{help} {label}" if help is not None else label
|
||||
|
||||
self.prompt = prompt_text
|
||||
self.confirmation_prompt = confirmation_prompt
|
||||
|
|
@ -2777,10 +2853,13 @@ class Option(Parameter):
|
|||
# Implicitly a flag because secondary options names were given.
|
||||
elif self.secondary_opts:
|
||||
is_flag = True
|
||||
# The option is explicitly not a flag. But we do not know yet if it needs a
|
||||
# value or not. So we look at the default value to determine it.
|
||||
|
||||
# The option is explicitly not a flag, but to determine whether or not it needs
|
||||
# value, we need to check if `flag_value` or `default` was set. Either one is
|
||||
# sufficient.
|
||||
# Ref: https://github.com/pallets/click/issues/3084
|
||||
elif is_flag is False and not self._flag_needs_value:
|
||||
self._flag_needs_value = self.default is UNSET
|
||||
self._flag_needs_value = flag_value is not UNSET or self.default is UNSET
|
||||
|
||||
if is_flag:
|
||||
# Set missing default for flags if not explicitly required or prompted.
|
||||
|
|
@ -2792,13 +2871,26 @@ class Option(Parameter):
|
|||
if type is None:
|
||||
# A flag without a flag_value is a boolean flag.
|
||||
if flag_value is UNSET:
|
||||
self.type: types.ParamType = types.BoolParamType()
|
||||
self.type: types.ParamType[t.Any] = types.BoolParamType()
|
||||
# If the flag value is a boolean, use BoolParamType.
|
||||
elif isinstance(flag_value, bool):
|
||||
self.type = types.BoolParamType()
|
||||
# Otherwise, guess the type from the flag value.
|
||||
else:
|
||||
self.type = types.convert_type(None, flag_value)
|
||||
guessed = types.convert_type(None, flag_value)
|
||||
if (
|
||||
isinstance(guessed, types.StringParamType)
|
||||
and not isinstance(flag_value, str)
|
||||
and flag_value is not None
|
||||
):
|
||||
# The flag_value type couldn't be auto-detected
|
||||
# (not str, int, float, or bool). Since flag_value
|
||||
# is a programmer-provided Python object, not CLI
|
||||
# input, pass it through unchanged instead of
|
||||
# stringifying it.
|
||||
self.type = types.UNPROCESSED
|
||||
else:
|
||||
self.type = guessed
|
||||
|
||||
self.is_flag: bool = bool(is_flag)
|
||||
self.is_bool_flag: bool = bool(
|
||||
|
|
@ -2811,14 +2903,12 @@ class Option(Parameter):
|
|||
if self.default is UNSET and not self.required:
|
||||
self.default = False
|
||||
|
||||
# Support the special case of aligning the default value with the flag_value
|
||||
# for flags whose default is explicitly set to True. Note that as long as we
|
||||
# have this condition, there is no way a flag can have a default set to True,
|
||||
# and a flag_value set to something else. Refs:
|
||||
# The alignment of default to the flag_value is resolved lazily in
|
||||
# get_default() to prevent callable flag_values (like classes) from
|
||||
# being instantiated. Refs:
|
||||
# https://github.com/pallets/click/issues/3121
|
||||
# https://github.com/pallets/click/issues/3024#issuecomment-3146199461
|
||||
# https://github.com/pallets/click/pull/3030/commits/06847da
|
||||
if self.default is True and self.flag_value is not UNSET:
|
||||
self.default = self.flag_value
|
||||
|
||||
# Set the default flag_value if it is not set.
|
||||
if self.flag_value is UNSET:
|
||||
|
|
@ -2882,7 +2972,40 @@ class Option(Parameter):
|
|||
)
|
||||
return info_dict
|
||||
|
||||
def get_error_hint(self, ctx: Context) -> str:
|
||||
def get_default(
|
||||
self, ctx: Context, call: bool = True
|
||||
) -> t.Any | t.Callable[[], t.Any] | None:
|
||||
"""Return the default value for this option.
|
||||
|
||||
For non-boolean flag options, ``default=True`` is treated as a sentinel
|
||||
meaning "activate this flag by default" and is resolved to
|
||||
:attr:`flag_value`. For example, with ``--upper/--lower`` feature
|
||||
switches where ``flag_value="upper"`` and ``default=True``, the default
|
||||
resolves to ``"upper"``.
|
||||
|
||||
.. caution::
|
||||
This substitution only applies to non-boolean flags
|
||||
(:attr:`is_bool_flag` is ``False``). For boolean flags, ``True`` is
|
||||
a legitimate Python value and ``default=True`` is returned as-is.
|
||||
|
||||
.. versionchanged:: 8.3.3
|
||||
``default=True`` is no longer substituted with ``flag_value`` for
|
||||
boolean flags, fixing negative boolean flags like
|
||||
``flag_value=False, default=True``.
|
||||
"""
|
||||
value = super().get_default(ctx, call=False)
|
||||
|
||||
# Resolve default=True to flag_value lazily (here instead of
|
||||
# __init__) to prevent callable flag_values (like classes) from
|
||||
# being instantiated by the callable check below.
|
||||
if value is True and self.is_flag and not self.is_bool_flag:
|
||||
value = self.flag_value
|
||||
elif call and callable(value):
|
||||
value = value()
|
||||
|
||||
return value
|
||||
|
||||
def get_error_hint(self, ctx: Context | None) -> str:
|
||||
result = super().get_error_hint(ctx)
|
||||
if self.show_envvar and self.envvar is not None:
|
||||
result += f" (env var: '{self.envvar}')"
|
||||
|
|
@ -2890,7 +3013,7 @@ class Option(Parameter):
|
|||
|
||||
def _parse_decls(
|
||||
self, decls: cabc.Sequence[str], expose_value: bool
|
||||
) -> tuple[str | None, list[str], list[str]]:
|
||||
) -> tuple[str, list[str], list[str]]:
|
||||
opts = []
|
||||
secondary_opts = []
|
||||
name = None
|
||||
|
|
@ -2899,7 +3022,7 @@ class Option(Parameter):
|
|||
for decl in decls:
|
||||
if decl.isidentifier():
|
||||
if name is not None:
|
||||
raise TypeError(f"Name '{name}' defined twice")
|
||||
raise TypeError(_("Name '{name}' defined twice").format(name=name))
|
||||
name = decl
|
||||
else:
|
||||
split_char = ";" if decl[:1] == "/" else "/"
|
||||
|
|
@ -2914,8 +3037,10 @@ class Option(Parameter):
|
|||
secondary_opts.append(second.lstrip())
|
||||
if first == second:
|
||||
raise ValueError(
|
||||
f"Boolean option {decl!r} cannot use the"
|
||||
" same flag for true/false."
|
||||
_(
|
||||
"Boolean option {decl!r} cannot use the"
|
||||
" same flag for true/false."
|
||||
).format(decl=decl)
|
||||
)
|
||||
else:
|
||||
possible_names.append(_split_opt(decl))
|
||||
|
|
@ -2929,16 +3054,20 @@ class Option(Parameter):
|
|||
|
||||
if name is None:
|
||||
if not expose_value:
|
||||
return None, opts, secondary_opts
|
||||
return "", opts, secondary_opts
|
||||
raise TypeError(
|
||||
f"Could not determine name for option with declarations {decls!r}"
|
||||
_(
|
||||
"Could not determine name for option with declarations {decls!r}"
|
||||
).format(decls=decls)
|
||||
)
|
||||
|
||||
if not opts and not secondary_opts:
|
||||
raise TypeError(
|
||||
f"No options defined but a name was passed ({name})."
|
||||
" Did you mean to declare an argument instead? Did"
|
||||
f" you mean to pass '--{name}'?"
|
||||
_(
|
||||
"No options defined but a name was passed ({name})."
|
||||
" Did you mean to declare an argument instead? Did"
|
||||
" you mean to pass '--{name}'?"
|
||||
).format(name=name)
|
||||
)
|
||||
|
||||
return name, opts, secondary_opts
|
||||
|
|
@ -3037,7 +3166,7 @@ class Option(Parameter):
|
|||
if (
|
||||
self.allow_from_autoenv
|
||||
and ctx.auto_envvar_prefix is not None
|
||||
and self.name is not None
|
||||
and self.name
|
||||
):
|
||||
envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
|
||||
|
||||
|
|
@ -3088,7 +3217,7 @@ class Option(Parameter):
|
|||
)[1]
|
||||
elif self.is_bool_flag and not self.secondary_opts and not default_value:
|
||||
default_string = ""
|
||||
elif default_value == "":
|
||||
elif isinstance(default_value, str) and default_value == "":
|
||||
default_string = '""'
|
||||
else:
|
||||
default_string = str(default_value)
|
||||
|
|
@ -3142,10 +3271,10 @@ class Option(Parameter):
|
|||
default = bool(default)
|
||||
return confirm(self.prompt, default)
|
||||
|
||||
# If show_default is set to True/False, provide this to `prompt` as well. For
|
||||
# non-bool values of `show_default`, we use `prompt`'s default behavior
|
||||
# If show_default is given, provide this to `prompt` as well,
|
||||
# otherwise we use `prompt`'s default behavior
|
||||
prompt_kwargs: t.Any = {}
|
||||
if isinstance(self.show_default, bool):
|
||||
if self.show_default is not None:
|
||||
prompt_kwargs["show_default"] = self.show_default
|
||||
|
||||
return prompt(
|
||||
|
|
@ -3176,11 +3305,7 @@ class Option(Parameter):
|
|||
if rv is not None:
|
||||
return rv
|
||||
|
||||
if (
|
||||
self.allow_from_autoenv
|
||||
and ctx.auto_envvar_prefix is not None
|
||||
and self.name is not None
|
||||
):
|
||||
if self.allow_from_autoenv and ctx.auto_envvar_prefix is not None and self.name:
|
||||
envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
|
||||
rv = os.environ.get(envvar)
|
||||
|
||||
|
|
@ -3263,7 +3388,7 @@ class Option(Parameter):
|
|||
self.is_flag
|
||||
and value is True
|
||||
and not self.is_bool_flag
|
||||
and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
|
||||
and source < ParameterSource.DEFAULT_MAP
|
||||
):
|
||||
value = self.flag_value
|
||||
|
||||
|
|
@ -3273,7 +3398,8 @@ class Option(Parameter):
|
|||
elif (
|
||||
self.multiple
|
||||
and value is not UNSET
|
||||
and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
|
||||
and isinstance(value, cabc.Iterable)
|
||||
and source < ParameterSource.DEFAULT_MAP
|
||||
and any(v is FLAG_NEEDS_VALUE for v in value)
|
||||
):
|
||||
value = [self.flag_value if v is FLAG_NEEDS_VALUE else v for v in value]
|
||||
|
|
@ -3282,10 +3408,7 @@ class Option(Parameter):
|
|||
# The value wasn't set, or used the param's default, prompt for one to the user
|
||||
# if prompting is enabled.
|
||||
elif (
|
||||
(
|
||||
value is UNSET
|
||||
or source in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
|
||||
)
|
||||
(value is UNSET or source >= ParameterSource.DEFAULT_MAP)
|
||||
and self.prompt is not None
|
||||
and (self.required or self.prompt_required)
|
||||
and not ctx.resilient_parsing
|
||||
|
|
@ -3348,14 +3471,14 @@ class Argument(Parameter):
|
|||
def human_readable_name(self) -> str:
|
||||
if self.metavar is not None:
|
||||
return self.metavar
|
||||
return self.name.upper() # type: ignore
|
||||
return self.name.upper()
|
||||
|
||||
def make_metavar(self, ctx: Context) -> str:
|
||||
if self.metavar is not None:
|
||||
return self.metavar
|
||||
var = self.type.get_metavar(param=self, ctx=ctx)
|
||||
if not var:
|
||||
var = self.name.upper() # type: ignore
|
||||
var = self.name.upper()
|
||||
if self.deprecated:
|
||||
var += "!"
|
||||
if not self.required:
|
||||
|
|
@ -3366,26 +3489,30 @@ class Argument(Parameter):
|
|||
|
||||
def _parse_decls(
|
||||
self, decls: cabc.Sequence[str], expose_value: bool
|
||||
) -> tuple[str | None, list[str], list[str]]:
|
||||
) -> tuple[str, list[str], list[str]]:
|
||||
if not decls:
|
||||
if not expose_value:
|
||||
return None, [], []
|
||||
return "", [], []
|
||||
raise TypeError("Argument is marked as exposed, but does not have a name.")
|
||||
if len(decls) == 1:
|
||||
name = arg = decls[0]
|
||||
name = name.replace("-", "_").lower()
|
||||
else:
|
||||
raise TypeError(
|
||||
"Arguments take exactly one parameter declaration, got"
|
||||
f" {len(decls)}: {decls}."
|
||||
_(
|
||||
"Arguments take exactly one parameter declaration, got"
|
||||
" {length}: {decls}."
|
||||
).format(length=len(decls), decls=decls)
|
||||
)
|
||||
return name, [arg], []
|
||||
|
||||
def get_usage_pieces(self, ctx: Context) -> list[str]:
|
||||
return [self.make_metavar(ctx)]
|
||||
|
||||
def get_error_hint(self, ctx: Context) -> str:
|
||||
return f"'{self.make_metavar(ctx)}'"
|
||||
def get_error_hint(self, ctx: Context | None) -> str:
|
||||
if ctx is not None:
|
||||
return f"'{self.make_metavar(ctx)}'"
|
||||
return f"'{self.human_readable_name}'"
|
||||
|
||||
def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None:
|
||||
parser.add_argument(dest=self.name, nargs=self.nargs, obj=self)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue