Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show help text with invalid default #1916

Merged
merged 1 commit into from May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -25,6 +25,8 @@ Unreleased
``default_map`` lookups. When using patterns like ``AliasedGroup``,
override ``resolve_command`` to change the name that is returned if
needed. :issue:`1895`
- If a default value is invalid, it does not prevent showing help
text. :issue:`1889`


Version 8.0.0
Expand Down
28 changes: 24 additions & 4 deletions src/click/core.py
Expand Up @@ -2196,6 +2196,10 @@ def get_default(
:param call: If the default is a callable, call it. Disable to
return the callable instead.

.. versionchanged:: 8.0.1
Type casting can fail in resilient parsing mode. Invalid
defaults will not prevent showing help text.

.. versionchanged:: 8.0
Looks at ``ctx.default_map`` first.

Expand All @@ -2214,7 +2218,13 @@ def get_default(

value = value()

return self.type_cast_value(ctx, value)
try:
return self.type_cast_value(ctx, value)
except BadParameter:
if ctx.resilient_parsing:
return value

raise

def add_to_parser(self, parser: OptionParser, ctx: Context) -> None:
raise NotImplementedError()
Expand Down Expand Up @@ -2700,14 +2710,24 @@ def _write_opts(opts: t.Sequence[str]) -> str:
)
extra.append(_("env var: {var}").format(var=var_str))

default_value = self.get_default(ctx, call=False)
# Temporarily enable resilient parsing to avoid type casting
# failing for the default. Might be possible to extend this to
# help formatting in general.
resilient = ctx.resilient_parsing
ctx.resilient_parsing = True

try:
default_value = self.get_default(ctx, call=False)
finally:
ctx.resilient_parsing = resilient

show_default_is_str = isinstance(self.show_default, str)

if show_default_is_str or (
default_value is not None and (self.show_default or ctx.show_default)
):
if show_default_is_str:
default_string: t.Union[str, t.Any] = f"({self.show_default})"
default_string = f"({self.show_default})"
elif isinstance(default_value, (list, tuple)):
default_string = ", ".join(str(d) for d in default_value)
elif callable(default_value):
Expand All @@ -2719,7 +2739,7 @@ def _write_opts(opts: t.Sequence[str]) -> str:
(self.opts if self.default else self.secondary_opts)[0]
)[1]
else:
default_string = default_value
default_string = str(default_value)

extra.append(_("default: {default}").format(default=default_string))

Expand Down
17 changes: 17 additions & 0 deletions tests/test_basic.py
Expand Up @@ -547,3 +547,20 @@ def cmd():
result = runner.invoke(cli, ["--help"])
assert "Summary line without period" in result.output
assert "Here is a sentence." not in result.output


def test_help_invalid_default(runner):
cli = click.Command(
"cli",
params=[
click.Option(
["-a"],
type=click.Path(exists=True),
default="not found",
show_default=True,
),
],
)
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
assert "default: not found" in result.output
2 changes: 1 addition & 1 deletion tests/test_options.py
Expand Up @@ -553,7 +553,7 @@ def cmd(config):

def test_argument_custom_class(runner):
class CustomArgument(click.Argument):
def get_default(self, ctx):
def get_default(self, ctx, call=True):
"""a dumb override of a default value for testing"""
return "I am a default"

Expand Down