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
9 changes: 6 additions & 3 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -708,12 +708,15 @@ def lookup_default(self, name: str, call: bool = True) -> t.Any | None:
if self.default_map is not None:
value = self.default_map.get(name, UNSET)

if value is UNSET:
return None

if call and callable(value):
return value()

return value

return UNSET
return None

def fail(self, message: str) -> t.NoReturn:
"""Aborts the execution of the program with a specific error
Expand Down Expand Up @@ -2280,7 +2283,7 @@ def get_default(
"""
value = ctx.lookup_default(self.name, call=False) # type: ignore

if value is UNSET:
if value is None:
value = self.default

if call and callable(value):
Expand Down Expand Up @@ -2322,7 +2325,7 @@ def consume_value(

if value is UNSET:
default_map_value = ctx.lookup_default(self.name) # type: ignore
if default_map_value is not UNSET:
if default_map_value is not None:
value = default_map_value
source = ParameterSource.DEFAULT_MAP

Expand Down
46 changes: 46 additions & 0 deletions tests/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,3 +780,49 @@ def test_propagate_opt_prefixes():
ctx = click.Context(click.Command("test2"), parent=parent)

assert ctx._opt_prefixes == {"-", "--", "!"}


def test_lookup_default_returns_none_when_not_in_map():
"""lookup_default should return None, not a sentinel, when the name
is not found in the default_map. Regression test for #3145."""
cmd = click.Command("test")
ctx = click.Context(cmd, info_name="test")
ctx.default_map = {"other_param": "value"}

# When the parameter is not in the default_map, should return None
result = ctx.lookup_default("missing_param")
assert result is None, f"Expected None, got {result!r} ({type(result)})"

result_no_call = ctx.lookup_default("missing_param", call=False)
assert result_no_call is None, f"Expected None, got {result_no_call!r}"


def test_lookup_default_returns_none_when_no_default_map():
"""lookup_default should return None when default_map is None."""
cmd = click.Command("test")
ctx = click.Context(cmd, info_name="test")
assert ctx.default_map is None

result = ctx.lookup_default("any_param")
assert result is None, f"Expected None, got {result!r} ({type(result)})"


def test_lookup_default_returns_value_when_in_map():
"""lookup_default should return the value when found in default_map."""
cmd = click.Command("test")
ctx = click.Context(cmd, info_name="test")
ctx.default_map = {"my_param": "my_value"}

assert ctx.lookup_default("my_param") == "my_value"


def test_lookup_default_calls_callable_when_call_true():
"""lookup_default should call callable defaults when call=True."""
cmd = click.Command("test")
ctx = click.Context(cmd, info_name="test")
ctx.default_map = {"my_param": lambda: "computed"}

assert ctx.lookup_default("my_param", call=True) == "computed"
# call=False should return the callable itself
result = ctx.lookup_default("my_param", call=False)
assert callable(result)
Loading