Skip to content

Commit

Permalink
🐛 Fix help message for Option with optional value
Browse files Browse the repository at this point in the history
  • Loading branch information
MatteoBouvier committed Nov 26, 2024
1 parent ebe64ec commit e8b83d6
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 92 deletions.
2 changes: 1 addition & 1 deletion docs/tutorial/options/optional_value.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Hello Camila Gutiérrez
// The above is equivalent to passing the --greeting CLI option with value `formal`
$ python main.py Camila Gutiérrez --greeting formal

Hi Camila !
Hello Camila Gutiérrez

// But you can select another value
$ python main.py Camila Gutiérrez --greeting casual
Expand Down
124 changes: 33 additions & 91 deletions typer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from datetime import datetime
from enum import Enum
from functools import update_wrapper
from gettext import gettext
from pathlib import Path
from traceback import FrameSummary, StackSummary
from types import TracebackType
Expand Down Expand Up @@ -838,122 +837,67 @@ def lenient_issubclass(
return isinstance(cls, type) and issubclass(cls, class_or_tuple)


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

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

@override
def to_info_dict(self) -> Dict[str, Any]:
info_dict: Dict[str, Any] = {}
for t in self._types:
info_dict |= t.to_info_dict()

return info_dict
return self._type.to_info_dict()

@override
def get_metavar(self, param: click.Parameter) -> Optional[str]:
metavar_union: list[str] = []
for t in self._types:
metavar = t.get_metavar(param)
if metavar is not None:
metavar_union.append(metavar)

if not len(metavar_union):
return None

return "|".join(metavar_union)
return self._type.get_metavar(param)

@override
def get_missing_message(self, param: click.Parameter) -> Optional[str]:
message_union: list[str] = []
for t in self._types:
message = t.get_missing_message(param)
if message is not None:
message_union.append(message)

if not len(message_union):
return None

return "\n".join(message_union)

@override
def convert(
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
) -> Any:
fail_messages: list[str] = []

for t in self._types:
try:
return t.convert(value, param, ctx)

except click.BadParameter as e:
if not getattr(t, "union_ignore_fail_message", False):
fail_messages.append(e.message)

self.fail(" and ".join(fail_messages), param, ctx)


class BoolLiteral(click.types.BoolParamType):
union_ignore_fail_message: bool = True
name: str = "boolean literal"
return self._type.get_missing_message(param)

@override
def convert(
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
) -> Any:
value_ = str(value)
norm = value_.strip().lower()
str_value = str(value).strip().lower()

# do not cast "1"
if norm in {"True", "true", "t", "yes", "y", "on"}:
return True
if str_value in {"True", "true", "t", "yes", "y", "on"}:
return self._default

# do not cast "0"
if norm in {"False", "false", "f", "no", "n", "off"}:
if str_value in {"False", "false", "f", "no", "n", "off"}:
return False

self.fail(
gettext("{value!r} is not a valid boolean literal.").format(value=value_),
param,
ctx,
)
if isinstance(value, DefaultFalse):
return False

@override
def __repr__(self) -> str:
return "BOOL(Literal)"
try:
return self._type.convert(value, param, ctx)

except click.BadParameter as e:
fail = e

class BoolInteger(click.ParamType):
union_ignore_fail_message: bool = True
name: str = "boolean integer"
if str_value == "1":
return self._default

@override
def convert(
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
) -> Any:
value_ = str(value)
norm = value_.strip()
if str_value == "0":
return False

if norm == "1":
return True
raise fail

if norm == "0":
return False

self.fail(
gettext("{value!r} is not a valid boolean integer.").format(value=value_),
param,
ctx,
)
class DefaultFalse:
def __init__(self, value: Any) -> None:
self._value = value

@override
def __repr__(self) -> str:
return "BOOL(int)"
return f"False ({repr(self._value)})"

def __str__(self) -> str:
return f"False ({str(self._value)})"


def get_click_param(
Expand Down Expand Up @@ -1051,11 +995,9 @@ def get_click_param(
elif secondary_type is bool:
is_flag = False
flag_value = default_value
default_value = False
assert parameter_type is not None
parameter_type = ClickTypeUnion(
BoolLiteral(), parameter_type, BoolInteger()
)
parameter_type = DefaultOption(parameter_type, default=default_value)
default_value = DefaultFalse(default_value)

default_option_name = get_command_name(param.name)
if is_flag:
Expand Down

0 comments on commit e8b83d6

Please sign in to comment.