Skip to content

Commit e8b83d6

Browse files
committed
🐛 Fix help message for Option with optional value
1 parent ebe64ec commit e8b83d6

File tree

2 files changed

+34
-92
lines changed

2 files changed

+34
-92
lines changed

docs/tutorial/options/optional_value.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Hello Camila Gutiérrez
5454
// The above is equivalent to passing the --greeting CLI option with value `formal`
5555
$ python main.py Camila Gutiérrez --greeting formal
5656

57-
Hi Camila !
57+
Hello Camila Gutiérrez
5858

5959
// But you can select another value
6060
$ python main.py Camila Gutiérrez --greeting casual

typer/main.py

Lines changed: 33 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from datetime import datetime
99
from enum import Enum
1010
from functools import update_wrapper
11-
from gettext import gettext
1211
from pathlib import Path
1312
from traceback import FrameSummary, StackSummary
1413
from types import TracebackType
@@ -838,122 +837,67 @@ def lenient_issubclass(
838837
return isinstance(cls, type) and issubclass(cls, class_or_tuple)
839838

840839

841-
class ClickTypeUnion(click.ParamType):
842-
def __init__(self, *types: click.ParamType) -> None:
843-
self._types: tuple[click.ParamType, ...] = types
844-
self.name: str = "|".join(t.name for t in types)
840+
class DefaultOption(click.ParamType):
841+
def __init__(self, type_: click.ParamType, default: Any) -> None:
842+
self._type: click.ParamType = type_
843+
self._default: Any = default
844+
self.name: str = f"BOOLEAN|{type_.name}"
845845

846846
@override
847847
def __repr__(self) -> str:
848-
return "|".join(repr(t) for t in self._types)
848+
return f"DefaultOption({self._type})"
849849

850850
@override
851851
def to_info_dict(self) -> Dict[str, Any]:
852-
info_dict: Dict[str, Any] = {}
853-
for t in self._types:
854-
info_dict |= t.to_info_dict()
855-
856-
return info_dict
852+
return self._type.to_info_dict()
857853

858854
@override
859855
def get_metavar(self, param: click.Parameter) -> Optional[str]:
860-
metavar_union: list[str] = []
861-
for t in self._types:
862-
metavar = t.get_metavar(param)
863-
if metavar is not None:
864-
metavar_union.append(metavar)
865-
866-
if not len(metavar_union):
867-
return None
868-
869-
return "|".join(metavar_union)
856+
return self._type.get_metavar(param)
870857

871858
@override
872859
def get_missing_message(self, param: click.Parameter) -> Optional[str]:
873-
message_union: list[str] = []
874-
for t in self._types:
875-
message = t.get_missing_message(param)
876-
if message is not None:
877-
message_union.append(message)
878-
879-
if not len(message_union):
880-
return None
881-
882-
return "\n".join(message_union)
883-
884-
@override
885-
def convert(
886-
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
887-
) -> Any:
888-
fail_messages: list[str] = []
889-
890-
for t in self._types:
891-
try:
892-
return t.convert(value, param, ctx)
893-
894-
except click.BadParameter as e:
895-
if not getattr(t, "union_ignore_fail_message", False):
896-
fail_messages.append(e.message)
897-
898-
self.fail(" and ".join(fail_messages), param, ctx)
899-
900-
901-
class BoolLiteral(click.types.BoolParamType):
902-
union_ignore_fail_message: bool = True
903-
name: str = "boolean literal"
860+
return self._type.get_missing_message(param)
904861

905862
@override
906863
def convert(
907864
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
908865
) -> Any:
909-
value_ = str(value)
910-
norm = value_.strip().lower()
866+
str_value = str(value).strip().lower()
911867

912-
# do not cast "1"
913-
if norm in {"True", "true", "t", "yes", "y", "on"}:
914-
return True
868+
if str_value in {"True", "true", "t", "yes", "y", "on"}:
869+
return self._default
915870

916-
# do not cast "0"
917-
if norm in {"False", "false", "f", "no", "n", "off"}:
871+
if str_value in {"False", "false", "f", "no", "n", "off"}:
918872
return False
919873

920-
self.fail(
921-
gettext("{value!r} is not a valid boolean literal.").format(value=value_),
922-
param,
923-
ctx,
924-
)
874+
if isinstance(value, DefaultFalse):
875+
return False
925876

926-
@override
927-
def __repr__(self) -> str:
928-
return "BOOL(Literal)"
877+
try:
878+
return self._type.convert(value, param, ctx)
929879

880+
except click.BadParameter as e:
881+
fail = e
930882

931-
class BoolInteger(click.ParamType):
932-
union_ignore_fail_message: bool = True
933-
name: str = "boolean integer"
883+
if str_value == "1":
884+
return self._default
934885

935-
@override
936-
def convert(
937-
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
938-
) -> Any:
939-
value_ = str(value)
940-
norm = value_.strip()
886+
if str_value == "0":
887+
return False
941888

942-
if norm == "1":
943-
return True
889+
raise fail
944890

945-
if norm == "0":
946-
return False
947891

948-
self.fail(
949-
gettext("{value!r} is not a valid boolean integer.").format(value=value_),
950-
param,
951-
ctx,
952-
)
892+
class DefaultFalse:
893+
def __init__(self, value: Any) -> None:
894+
self._value = value
953895

954-
@override
955896
def __repr__(self) -> str:
956-
return "BOOL(int)"
897+
return f"False ({repr(self._value)})"
898+
899+
def __str__(self) -> str:
900+
return f"False ({str(self._value)})"
957901

958902

959903
def get_click_param(
@@ -1051,11 +995,9 @@ def get_click_param(
1051995
elif secondary_type is bool:
1052996
is_flag = False
1053997
flag_value = default_value
1054-
default_value = False
1055998
assert parameter_type is not None
1056-
parameter_type = ClickTypeUnion(
1057-
BoolLiteral(), parameter_type, BoolInteger()
1058-
)
999+
parameter_type = DefaultOption(parameter_type, default=default_value)
1000+
default_value = DefaultFalse(default_value)
10591001

10601002
default_option_name = get_command_name(param.name)
10611003
if is_flag:

0 commit comments

Comments
 (0)