diff --git a/CHANGES.rst b/CHANGES.rst index 0fce1bc22..adb580532 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,8 @@ Unreleased ``Context.invoked_subcommand`` consistent when using patterns like ``AliasedGroup``. :issue:`1422` - The ``BOOL`` type accepts the values "on" and "off". :issue:`1629` +- A ``Group`` with ``invoke_without_command=True`` will always invoke + its result callback. :issue:`1178` Version 7.1.2 diff --git a/src/click/core.py b/src/click/core.py index 22f742001..9651deb65 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1170,7 +1170,7 @@ def format_options(self, ctx, formatter): self.format_commands(ctx, formatter) def resultcallback(self, replace=False): - """Adds a result callback to the chain command. By default if a + """Adds a result callback to the command. By default if a result callback is already registered this will chain them but this can be disabled with the `replace` parameter. The result callback is invoked with the return value of the subcommand @@ -1189,10 +1189,10 @@ def cli(input): def process_result(result, input): return result + input - .. versionadded:: 3.0 - :param replace: if set to `True` an already existing result callback will be removed. + + .. versionadded:: 3.0 """ def decorator(f): @@ -1258,18 +1258,13 @@ def _process_result(value): return value if not ctx.protected_args: - # If we are invoked without command the chain flag controls - # how this happens. If we are not in chain mode, the return - # value here is the return value of the command. - # If however we are in chain mode, the return value is the - # return value of the result processor invoked with an empty - # list (which means that no subcommand actually was executed). if self.invoke_without_command: - if not self.chain: - return Command.invoke(self, ctx) + # No subcommand was invoked, so the result callback is + # invoked with None for regular groups, or an empty list + # for chained groups. with ctx: Command.invoke(self, ctx) - return _process_result([]) + return _process_result([] if self.chain else None) ctx.fail("Missing command.") # Fetch args back out diff --git a/tests/test_chain.py b/tests/test_chain.py index 046277909..74a04f106 100644 --- a/tests/test_chain.py +++ b/tests/test_chain.py @@ -88,6 +88,25 @@ def bdist(format): assert result.output.splitlines() == ["bdist called 1", "sdist called 2"] +@pytest.mark.parametrize(("chain", "expect"), [(False, "None"), (True, "[]")]) +def test_no_command_result_callback(runner, chain, expect): + """When a group has ``invoke_without_command=True``, the result + callback is always invoked. A regular group invokes it with + ``None``, a chained group with ``[]``. + """ + + @click.group(invoke_without_command=True, chain=chain) + def cli(): + pass + + @cli.resultcallback() + def process_result(result): + click.echo(str(result), nl=False) + + result = runner.invoke(cli, []) + assert result.output == expect + + def test_chaining_with_arguments(runner): @click.group(chain=True) def cli():