Skip to content

Commit

Permalink
Merge pull request #1621 from IamCathal/result-callback-invocation-1178
Browse files Browse the repository at this point in the history
non-chained group invoked without subcommand invokes result callback
  • Loading branch information
davidism authored Aug 5, 2020
2 parents 2815455 + 32ddbb1 commit 6271cee
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 12 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 7 additions & 12 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions tests/test_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down

0 comments on commit 6271cee

Please sign in to comment.