Search code examples
pythoncommand-line-interfacepython-click

Invoking another subcommand with Python click doesn't seem to validate fields


If I invoke a Click subcommand from another command, it doesn't look like the invoked command is validating the passed fields. Am I doing something wrong?

This is a small example:

cli_main.py

import click
import cli_add
import cli_sub
import sys

@click.group()
@click.pass_context
def cli(ctx):
    """Main CLI"""

cli.add_command(cli_add.add)
cli.add_command(cli_sub.subtract)


if __name__ == "__main__":
    sys.exit(cli())

cli_sub.py

import click

@click.group()
@click.pass_context
def subtract(ctx):
    """subtract"""


@subtract.command()
@click.option('--number', type=int, required=True)
@click.pass_context
def number(ctx, number):
    click.echo("Subtract {}".format(number))

cli_add.py

import click
import cli_sub


@click.group()
@click.pass_context
def add(ctx):
    """add"""


@add.command()
@click.option('--number', type=int, required=True)
@click.pass_context
def number(ctx, number):
    if number > 0:
        click.echo("Add {}".format(number))
    else:
        ctx.invoke(cli_sub.number)

Example calls:

python cli_main.py add number --number 1
Add 1

python cli_main.py subtract number --number 1
Subtract 1

python cli_main.py subtract number
Usage: cli_main.py subtract number [OPTIONS]
Try "cli_main.py subtract number --help" for help.

Error: Missing option "--number".

These work as expected. However, if I call the following, it fails in an unexpected way.

python cli_main.py add number --number -1
Subtract None

It does invoke the subtraction method. The problem is that I am not passing a parameter in my call to it:

ctx.invoke(cli_sub.number)

So, I'd expect it to fail and provide me with the usage text, especially since the number option is required.

Am I not invoking this subcommand correctly? Why does the invocation not trigger parameter checks the same way directly calling the subcommand without the parameter does?


Solution

  • Your analysis of the problem is correct, you do need to pass the parameters. You can pass the parameter like:

    ctx.invoke(cli_sub.number, number=number)
    

    As to your other question:

    Why does the invocation not trigger parameter checks the same way directly calling the sub-command without the parameter does?

    This checking is done when parsing the parameters. If you are calling ctx.invoke() directly it is expected you understand what your command needs.