From 747dd2074991d3f2e3fa5b24db1040c2649b896c Mon Sep 17 00:00:00 2001 From: Friday Date: Tue, 17 Feb 2026 12:39:34 +0000 Subject: [PATCH] Fix callable flag_value being instantiated when used as default When `default=True` is set on a flag option with a callable `flag_value` (e.g. a class), the `default=True` alignment code replaces the default with the flag_value. Then `get_default()` sees `callable(value)` is True and calls it, instantiating the class instead of returning it as-is. Track when the default was set from the flag_value alignment and skip the callable check in that case. Fixes #3121 Co-Authored-By: Claude Opus 4.6 --- src/click/core.py | 33 +++++++++++++++++++++++++++++++++ tests/test_options.py | 4 ++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/click/core.py b/src/click/core.py index 6adc65ccd6..9485afa38e 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -2820,8 +2820,10 @@ def __init__( # and a flag_value set to something else. Refs: # https://github.com/pallets/click/issues/3024#issuecomment-3146199461 # https://github.com/pallets/click/pull/3030/commits/06847da + self._default_from_flag_value = False if self.default is True and self.flag_value is not UNSET: self.default = self.flag_value + self._default_from_flag_value = True # Set the default flag_value if it is not set. if self.flag_value is UNSET: @@ -2885,6 +2887,37 @@ def to_info_dict(self) -> dict[str, t.Any]: ) return info_dict + @t.overload + def get_default( + self, ctx: Context, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Any | t.Callable[[], t.Any] | None: + value = ctx.lookup_default(self.name, call=False) # type: ignore + + if value is UNSET: + value = self.default + + # Don't call the default if it was set from the flag_value via + # the ``default=True`` alignment. A callable flag_value (e.g. a + # class used as a flag value) should be returned as-is, not + # instantiated. See https://github.com/pallets/click/issues/3121 + if ( + call + and callable(value) + and not self._default_from_flag_value + ): + value = value() + + return value + def get_error_hint(self, ctx: Context) -> str: result = super().get_error_hint(ctx) if self.show_envvar and self.envvar is not None: diff --git a/tests/test_options.py b/tests/test_options.py index f198d10183..e2c473bba8 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -2148,7 +2148,7 @@ def scan(pro): {"flag_value": Class1, "default": True}, {"flag_value": Class2}, [], - re.compile(r"''"), + "", ), ( {"flag_value": Class1, "default": True}, @@ -2170,7 +2170,7 @@ def scan(pro): {"flag_value": Class1, "type": UNPROCESSED, "default": True}, {"flag_value": Class2, "type": UNPROCESSED}, [], - re.compile(r""), + Class1, ), ( {"flag_value": Class1, "type": UNPROCESSED, "default": True},