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.x

Unreleased

- If ``flag_value`` is set or no default is provided the flag can be accepted
without an argument.:issue:`3084` :pr:`#3104`
- Don't discard pager arguments by correctly using ``subprocess.Popen``. :issue:`3039`
:pr:`3055`
- Replace ``Sentinel.UNSET`` default values by ``None`` as they're passed through
Expand Down
13 changes: 10 additions & 3 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2654,6 +2654,10 @@ class Option(Parameter):
:param hidden: hide this option from help outputs.
:param attrs: Other command arguments described in :class:`Parameter`.

.. versionchanged:: 8.3.dev
If ``flag_value`` is set or no default is provided, the flag can be
accepted without an argument.

.. versionchanged:: 8.2
``envvar`` used with ``flag_value`` will always use the ``flag_value``,
previously it would use the value of the environment variable.
Expand Down Expand Up @@ -2747,10 +2751,13 @@ def __init__(
# 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.

# Handle options that are not flags but provide a flag_value.
# If flag_value is set or no default is provided the flag can be accepted
# without an argument.
# 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.
Expand Down
34 changes: 34 additions & 0 deletions tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from click import Option
from click import UNPROCESSED
from click._utils import UNSET
from click.testing import CliRunner


def test_prefixes(runner):
Expand Down Expand Up @@ -2275,3 +2276,36 @@ def rcli(scm_ignore_files):
result = runner.invoke(rcli, ["--without-scm-ignore-files"])
assert result.stdout == "frozenset()"
assert result.exit_code == 0


def test_flag_value_optional_behavior():
"""Test that options with flag_value and is_flag=False use flag_value
when only flag is provided

Reproduces https://github.com/pallets/click/issues/3084
"""

@click.command()
@click.option("--name", is_flag=False, flag_value="Flag", default="Default")
def hello(name):
click.echo(f"Hello, {name}!")

runner = CliRunner()
result = runner.invoke(hello, ["--name"])
assert result.exit_code == 0
assert result.output == "Hello, Flag!\n"


def test_flag_value_with_type_conversion():
"""Test that flag_value is correctly type-converted when used as an option value."""

@click.command()
@click.option("--count", is_flag=False, flag_value="1", type=int, default=0)
def repeat(count):
for i in range(count):
click.echo(f"Line {i + 1}")

runner = CliRunner()
result = runner.invoke(repeat, ["--count"])
assert result.exit_code == 0
assert result.output == "Line 1\n"