1

The click library discourages invoking the click.forward and click.forward functions to 'self-invoke' CLI commands. To quote the docs:

Sometimes, it might be interesting to invoke one command from another command. This is a pattern that is generally discouraged with Click, but possible nonetheless. For this, you can use the Context.invoke() or Context.forward() methods.

However, they do not offer alternatives.

For example, let's say we want to use our own CLI's call our_cli get user-funds to verify a user's funds before we execute the hypothetical our_cli buy some_item. How would be the way to do that without using Context.invoke or Context.forward?


PS: This is not a question about using the invoke and forward functions. That has already been discussed over here: link, link.

3
  • 2
    These sorts of questions are opinion based and are generally off-topic on SO. But generally, Click is a layer between the command line and Python code. If you are already executing Python code, why would you want to go back through the Click layer, when you can just call the Python code directly? As such, why would the Click coders spend timing building this capability? Commented Aug 22, 2021 at 12:45
  • 1
    Thanks for your constructive feedback @StephenRauch. I'll modify the question to be in line with your comment. Commented Aug 22, 2021 at 12:53
  • Updated the question @StephenRauch. It now asks for the alternative, instead of asking why this is discouraged. You already hinted at that in your comment: "call the Python code directly". If you want to explain why this is better in an answer, I'd be delighted to accept it. Commented Aug 22, 2021 at 13:14

1 Answer 1

1

Thanks to @StephenRauch for the tip of calling the python code directly.
Here is a simplified example that shows how to refactor a Context.invoke call to call python code directly.

Example

Assume that we invoked our CLI's get-user-funds command to get a user's budget before we buy a hypothetical item.

import click

# E.g., assume the price and funds come from some API
def get_funds(user): return 100  
def get_price(item): return 50   

@click.group()
@click.pass_context
def our_cli(ctx): 
    # To simplify, assume that the CLI already knows the relevant user.
    ctx.obj = {"user": "Cindy"} 


@our_cli.command()
@click.argument("user")
def get_user_funds(user):
    # Normally we would use this command to print the funds 
    # of a user to stdout.
    funds = get_funds(user)
    click.echo(f"{funds=}")
    return funds


@our_cli.command()
@click.argument("item")
@click.pass_context
def buy_item(ctx, item):
    # This is the `invoke` call that we wish to refactor.
    funds = ctx.invoke(get_user_funds)
    if funds >= get_price(item):
        print(f"bought {item}")
    else:
        print("f{funds}")


if __name__ == "__main__":
    our_cli()

Refactoring

Instead of calling Context.invoke to get the funds, we can also call the python code directly.
We can do that by rewriting buy_item as follows:

@our_cli.command()
@click.argument("item")
@click.pass_context
def buy_item(ctx: click.Context, item: str):
    # Now we call python code directly.
    funds = get_funds(ctx.obj["user"])
    # Note that bypass the click.echo(f"{funds=}") call now. This
    #  is probably something we would like in this example.
    if funds >= get_price(item):
        print(f"bought {item}")
    else:
        print("f{funds}")

Closing Remarks

In this example, the refactoring was very simple.
We already had a python function (i.e. get_funds) that we could call directly.
When working with more complex code, it is likely that you have to restructure your code.
In my case, among other things, I had to extract the logic that I wanted to call directly from a @click.command annotated function to a normal python function.
After that, I was able to replace the Context.invoke call with a direct function call.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.