From 63889fd0180b7eeacbf6e114d4fb95fa2f7326c0 Mon Sep 17 00:00:00 2001 From: Brandon Carpenter Date: Tue, 24 May 2022 10:59:52 -0700 Subject: [PATCH] Allow is_flag=True, multiple=True with non-bool flag_value Allows creating options with is_flag=True and multiple=True if flag_value is also set to a non-bool value. Fixes #2292 --- CHANGES.rst | 10 ++++++++++ src/click/core.py | 2 +- tests/test_options.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 17665672f..ebddf2922 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,15 @@ .. currentmodule:: click +Version 8.1.4 +------------- + +Released TBD + +- Do not show an error when attempting to create an option with + ``multiple=True, is_flag=True`` if ``flag_value`` is not a ``bool``. + :issue:`2292` + + Version 8.1.3 ------------- diff --git a/src/click/core.py b/src/click/core.py index 5abfb0f3c..3156de0d6 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -2580,7 +2580,7 @@ def __init__( if self.is_flag: raise TypeError("'count' is not valid with 'is_flag'.") - if self.multiple and self.is_flag: + if self.multiple and self.is_flag and isinstance(self.flag_value, bool): raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.") def to_info_dict(self) -> t.Dict[str, t.Any]: diff --git a/tests/test_options.py b/tests/test_options.py index d4090c725..1e920f332 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -922,3 +922,36 @@ def test_invalid_flag_combinations(runner, kwargs, message): click.Option(["-a"], **kwargs) assert message in str(e.value) + + +@pytest.mark.parametrize( + ("args", "expected"), + [ + ([], "tags= verbosity=0"), + (["--foo", "--bar", "--baz"], "tags=foo,bar,baz verbosity=0"), + (["--bar", "--foo", "-vv"], "tags=bar,foo verbosity=2"), + (["--verbose"], "tags= verbosity=1"), + (["--quiet"], "tags= verbosity=-1"), + (["--bar", "--foo", "-m", "foo", "-vvvq"], "tags=bar,foo,foo verbosity=2"), + ], +) +def test_multi_value_flags(runner, args, expected): + @click.command() + @click.option("--bar", "tags", flag_value="bar", multiple=True) + @click.option("--baz", "tags", flag_value="baz", multiple=True) + @click.option("--foo", "tags", flag_value="foo", multiple=True) + @click.option( + "-m", "--tag", "tags", type=click.Choice(["foo", "bar", "baz"]), multiple=True + ) + @click.option( + "-q", "--quiet", "verbosity", is_flag=True, flag_value=-1, multiple=True + ) + @click.option( + "-v", "--verbose", "verbosity", is_flag=True, flag_value=1, multiple=True + ) + def cli(tags, verbosity): + click.echo(f"tags={','.join(tags)} verbosity={sum(verbosity)}", nl=False) + + result = runner.invoke(cli, args) + assert not result.exception + assert result.output == expected