Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Version 8.3.2

Released 2026-04-02

- Show custom ``show_default`` string in prompts, matching the existing
help text behavior. :issue:`2836` :pr:`2837` :pr:`3165` :pr:`3262` :pr:`3280`
- Fix handling of ``flag_value`` when ``is_flag=False`` to allow such options to be
used without an explicit value. :issue:`3084` :pr:`3152`
- Hide ``Sentinel.UNSET`` values as ``None`` when using ``lookup_default()``.
Expand Down
5 changes: 2 additions & 3 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3164,10 +3164,9 @@ def prompt_for_value(self, ctx: Context) -> t.Any:
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 set to True/False/string, provide this to `prompt` as well.
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(
Expand Down
12 changes: 10 additions & 2 deletions src/click/termui.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@ def hidden_prompt_func(prompt: str) -> str:
def _build_prompt(
text: str,
suffix: str,
show_default: bool = False,
show_default: bool | str = False,
default: t.Any | None = None,
show_choices: bool = True,
type: ParamType | None = None,
) -> str:
prompt = text
if type is not None and show_choices and isinstance(type, Choice):
prompt += f" ({', '.join(map(str, type.choices))})"
if isinstance(show_default, str):
default = f"({show_default})"
if default is not None and show_default:
prompt = f"{prompt} [{_format_default(default)}]"
return f"{prompt}{suffix}"
Expand All @@ -88,7 +90,7 @@ def prompt(
type: ParamType | t.Any | None = None,
value_proc: t.Callable[[str], t.Any] | None = None,
prompt_suffix: str = ": ",
show_default: bool = True,
show_default: bool | str = True,
err: bool = False,
show_choices: bool = True,
) -> t.Any:
Expand All @@ -112,13 +114,19 @@ def prompt(
convert a value.
:param prompt_suffix: a suffix that should be added to the prompt.
:param show_default: shows or hides the default value in the prompt.
Can also be a string to show a custom value instead of the actual
default.
:param err: if set to true the file defaults to ``stderr`` instead of
``stdout``, the same as with echo.
:param show_choices: Show or hide choices if the passed type is a Choice.
For example if type is a Choice of either day or week,
show_choices is true and text is "Group by" then the
prompt will be "Group by (day, week): ".

.. versionchanged:: 8.3.3
``show_default`` can be a string to show a custom value instead
of the actual default, matching the help text behavior.

.. versionchanged:: 8.3.1
A space is no longer appended to the prompt.

Expand Down
53 changes: 53 additions & 0 deletions tests/test_termui.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import click._termui_impl
from click._compat import WIN
from click._utils import UNSET
from click.exceptions import BadParameter
from click.exceptions import MissingParameter

Expand Down Expand Up @@ -496,6 +497,58 @@ def cmd(arg1):
assert "my-default-value" not in result.output


@pytest.mark.parametrize(
("show_default", "default", "user_input", "in_prompt", "not_in_prompt"),
[
# Regular string replaces the actual default in the prompt.
("custom", "actual", "\n", "(custom)", "actual"),
# String with spaces.
("custom label", "actual", "\n", "(custom label)", "actual"),
# Unicode characters.
("∞", "0", "\n", "(∞)", None),
# Numeric default: custom string hides the number.
("unlimited", 42, "\n", "(unlimited)", "42"),
# Explicit default=None: custom string still appears, must provide input.
("computed at runtime", None, "value\n", "(computed at runtime)", None),
# No default kwarg at all (internal UNSET sentinel): same as None.
("computed at runtime", UNSET, "value\n", "(computed at runtime)", None),
# Empty string is falsy: suppresses any default display.
("", "actual", "\n", None, "actual"),
],
ids=[
"simple-string",
"string-with-spaces",
"unicode",
"numeric-default",
"default-is-none",
"default-is-unset",
"empty-string-is-falsy",
],
)
def test_string_show_default_in_prompt(
runner, show_default, default, user_input, in_prompt, not_in_prompt
):
"""When show_default is a string, the prompt should display that
string in parentheses instead of the actual default value,
matching the help text behavior. See pallets/click#2836."""

option_kwargs = {"show_default": show_default, "prompt": True}
if default is not UNSET:
option_kwargs["default"] = default

@click.command()
@click.option("--arg1", **option_kwargs)
def cmd(arg1):
click.echo(arg1)

result = runner.invoke(cmd, input=user_input, standalone_mode=False)
prompt_line = result.output.split("\n")[0]
if in_prompt is not None:
assert in_prompt in prompt_line
if not_in_prompt is not None:
assert not_in_prompt not in prompt_line


REPEAT = object()
"""Sentinel value to indicate that the prompt is expected to be repeated.

Expand Down