-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Open
Labels
Milestone
Description
Upgrading click to 8.3.0 or 8.3.1 causes a regression in lookup_default
Reproduce
Change the click dependency in the below reproduce case to 8.2.1 and the assertion does not fail.
Install uv. Run with uv run repro_click_unset_regression.py or simply `repro_click_unset_regression.py'.
#!/usr/bin/env -S uv run
# /// script
# dependencies = ["click==8.3.1"]
# ///
"""
Run with uv installed:
repro_click_unset_regression.py
Expected (pre 8.3.0): lookup_default returns the prefix-level default string or None.
Actual (8.3.0+): returns Sentinel.UNSET instead of None.
"""
import importlib.metadata
import sys
import click
class CustomClickContext(click.Context):
def lookup_default(self, name: str, call: bool = False):
if call:
default = super().lookup_default(name, call=True) # call: Literal[True]
else:
default = super().lookup_default(name, call=False) # call: Literal[False]
# Original logic (kept to reproduce): treats any non-None (including sentinel) as a real default.
if default is not None:
return default
prefix = name.split("_", 1)[0]
group = getattr(self, "default_map", {}).get(prefix)
if group:
return group.get(name)
return default
def describe(value):
cls = value.__class__
return {
"repr": repr(value),
"type": f"{cls.__module__}.{cls.__qualname__}",
"has_name_attr": hasattr(value, "name"),
"name_attr": getattr(value, "name", None),
}
def main():
default_map = {"app": {"email": "[email protected]"}}
cmd = click.Command("get-views")
ctx = CustomClickContext(cmd, info_name=None)
ctx.default_map = default_map
val = ctx.lookup_default("email", False)
info = describe(val)
print(f"click version (importlib.metadata): {importlib.metadata.version('click')}")
print("lookup_default raw:", info)
# This assert fails under ≥ 8.3.0 because val is the internal Sentinel.UNSET object.
assert val is None, (
f"Regression: got {info['repr']} ({info['type']}) instead of expected builtins.NoneType"
)
if __name__ == "__main__":
try:
main()
except AssertionError as e:
print("AssertionError:", e)
sys.exit(1)Expected
Return None instead of internal Sentinel.UNSET detail.
Environment:
- Python version: 3.14
- Click version: 8.30, 8.3.1