From b36e3ede781b388ae8135af61e83269e211acdf1 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 3 Jul 2023 09:05:56 -0700 Subject: [PATCH] allow multiple=True with flags co-authored-by: Amethyst Reese --- CHANGES.rst | 2 ++ src/click/core.py | 10 ++++++---- tests/test_defaults.py | 21 +++++++++++++++++++++ tests/test_options.py | 4 ---- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2debd7814..cf27f573a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,6 +22,8 @@ Unreleased ``standalone_mode`` is disabled. :issue:`2380` - ``@group.command`` does not fail if the group was created with a custom ``command_class``. :issue:`2416` +- ``multiple=True`` is allowed for flag options again and does not require + setting ``default=()``. :issue:`2246, 2292, 2295` Version 8.1.3 diff --git a/src/click/core.py b/src/click/core.py index b03a76039..cc65e896b 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -2570,8 +2570,13 @@ def __init__( # flag if flag_value is set. self._flag_needs_value = flag_value is not None + self.default: t.Union[t.Any, t.Callable[[], t.Any]] + if is_flag and default_is_missing and not self.required: - self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False + if multiple: + self.default = () + else: + self.default = False if flag_value is None: flag_value = not self.default @@ -2622,9 +2627,6 @@ def __init__( if self.is_flag: raise TypeError("'count' is not valid with 'is_flag'.") - if self.multiple and self.is_flag: - raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.") - def to_info_dict(self) -> t.Dict[str, t.Any]: info_dict = super().to_info_dict() info_dict.update( diff --git a/tests/test_defaults.py b/tests/test_defaults.py index 0e438eb89..8ef5ea292 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -38,3 +38,24 @@ def cli(arg): result = runner.invoke(cli, []) assert not result.exception assert result.output.splitlines() == ["<1|2>", "<3|4>"] + + +def test_multiple_flag_default(runner): + """Default default for flags when multiple=True should be empty tuple.""" + + @click.command + # flag due to secondary token + @click.option("-y/-n", multiple=True) + # flag due to is_flag + @click.option("-f", is_flag=True, multiple=True) + # flag due to flag_value + @click.option("-v", "v", flag_value=1, multiple=True) + @click.option("-q", "v", flag_value=-1, multiple=True) + def cli(y, f, v): + return y, f, v + + result = runner.invoke(cli, standalone_mode=False) + assert result.return_value == ((), (), ()) + + result = runner.invoke(cli, ["-y", "-n", "-f", "-v", "-q"], standalone_mode=False) + assert result.return_value == ((True, False), (True,), (1, -1)) diff --git a/tests/test_options.py b/tests/test_options.py index d4090c725..91249b234 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -911,10 +911,6 @@ def test_is_bool_flag_is_correctly_set(option, expected): [ ({"count": True, "multiple": True}, "'count' is not valid with 'multiple'."), ({"count": True, "is_flag": True}, "'count' is not valid with 'is_flag'."), - ( - {"multiple": True, "is_flag": True}, - "'multiple' is not valid with 'is_flag', use 'count'.", - ), ], ) def test_invalid_flag_combinations(runner, kwargs, message):