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

resultcallback is not being called when invoke_without_command=True #1178

Closed
trbedwards opened this issue Dec 3, 2018 · 7 comments · Fixed by #1621
Closed

resultcallback is not being called when invoke_without_command=True #1178

trbedwards opened this issue Dec 3, 2018 · 7 comments · Fixed by #1621
Labels
f:chain feature: chained commands
Milestone

Comments

@trbedwards
Copy link

trbedwards commented Dec 3, 2018

I have a group-command (called groupcmd) which I would like the user to be able to invoke without having to specify a subcommand. I also have a resultcallback (via @groupcmd.resultcallback) which I would like to call after the group-command finishes for processing errors and logging them to file.

If I set invoke_without_command=True inside of the @click.group definition, then the resultcallback does not get called.

I am using click 6.6 on python 2.7.12

@davidism
Copy link
Member

davidism commented Dec 3, 2018

Click 6 will only receive security fixes. Please verify that this still happens on Click 7.

@trbedwards
Copy link
Author

I've confirmed this happens with click 7.0 on python 3.5.2. Here is some minimal code to reproduce the issue (test.py):

import click

@click.group(invoke_without_command=True)
def main():
    print("Invoking main")
    return "main result"


@main.command()
def sub():
    print("Invoking sub")
    return "sub result"


@main.resultcallback()
def process_result(result):
    print("result was:", result)


if __name__ == '__main__':
    main()

If the subcommand is invoked (i.e, python test.py sub) then it will print result was: sub result but if main is invoked without a subcommand (i.e, python test.py) then it will not print the result.

@davidism
Copy link
Member

davidism commented Dec 3, 2018

I'm not familiar with resultcallback off the top of my head, but if you don't find anything in the docs contradicting the behavior you want, I'd be happy to review a PR.

@IamCathal
Copy link
Contributor

I'm looking into this issue at the moment

@IamCathal
Copy link
Contributor

The documentation states that resultcallback "Adds a result callback to the chain command."

Another example in the docs shows the result callback being invoked when using subcommands which is what it is meant for. From this I think that resultcallback is just used more or less for getting data from subcommands and not singular calls without any subcommands invoked which your script attempts to do.

@davidism
Copy link
Member

davidism commented Jul 2, 2020

@IamCathal further on in the doc for resultcallback, it calls out a difference in behavior for chaining, which seems to imply that a result callback should be called for either type of command. And that is the case, it's called for a group with a subcommand and for a chain.

Looking through the code, the picture gets more complicated. If invoke_without_command is triggered, the code specifically calls out only invoking the result callback for chained commands (with an empty list of results), while ignoring groups.

click/src/click/core.py

Lines 1261 to 1272 in 35f73b8

# 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)
with ctx:
Command.invoke(self, ctx)
return _process_result([])

So it seems like this is intentional behavior, but there's no explanation for why it was done. The best explanation I can guess is that in a chain you would get an empty list for no commands, but for a regular group you'd only get None and wouldn't be able to distinguish between no subcommand and one that returned None. But if you were using resultcallback with a regular group, you'd presumably be returning a value from subcommands, or not care if the result was None.

I'm ok with changing this behavior.

@davidism
Copy link
Member

davidism commented Mar 2, 2022

Going to modify this slightly in 8.1 based on #2124. If the group returns a value (and is not in chain mode), that value will be passed to the result callback, instead of ignoring it and always passing None. If you need to know whether the return value is from a subcommand, look at ctx.invoked_subcommand.

@group.result_callback
@click.pass_context
def group_result(ctx, value):
    if ctx.invoked_subcommand is None:
        # value was from group, not subcommand

In chain mode, the group return value is ignored, which was the behavior before this change.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
f:chain feature: chained commands
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants