1

I am using Python 3.9 and Click to build a small command line interface utility, but I am getting strange errors, specifically when I attempt to call one function decorated as a @click.command() from another function that is also decorated the same way.

I have distilled my program down to the bare minimum to explain what I mean.

This is my program

import click

@click.group()
def cli():
    pass

@click.command()
@click.argument('id')
def outer(id):
    print('outer id',id,type(id))
    inner(id)  # run inner() from within outer(), pass argument unchanged

@click.command()       # *
@click.argument('id')  # *
def inner(id):
    print('inner id',id,type(id))

cli.add_command(outer)
cli.add_command(inner)  # *

if __name__ == '__main__':
    cli()

This is the CLI result when I run my script using both the inner and outer commands:

% python3 test.py inner 123
inner id 123 <class 'str'>
% python3 test.py outer 123
outer id 123 <class 'str'>
Usage: test.py [OPTIONS] ID
Try 'test.py --help' for help.

Error: Got unexpected extra arguments (2 3)
%

Interestingly, it works when I use a single character argument:

% python3 test.py outer 1
outer id 1 <class 'str'>
inner id 1 <class 'str'>
%

If I comment out the three lines marked with # *, running the outer command behaves as expected, passing on the id argument to inner() without changes or issues, no matter the length of the argument:

% python3 test.py outer 123
outer id 123 <class 'str'>
inner id 123 <class 'str'>
%

Clearly, the decorators are somehow messing with the arguments. Can anybody explain why this is, and how I can achieve the desired behavior of passing on the arguments unchanged? Maybe I am missing something super obvious?

Thanks in advance!

1 Answer 1

2

Use the context operations to invoke other commands

import click

@click.group()
def cli():
    pass

@click.command()
@click.argument('id')
@click.pass_context
def outer(ctx, id):
    print('outer id',id,type(id))
    ctx.forward(inner)  # run inner(), pass argument unchanged
    ctx.invoke(inner, id=id+1) # run inner(), pass modified argument

@click.command()       # *
@click.argument('id')  # *
def inner(id):
    print('inner id',id,type(id))

cli.add_command(outer)
cli.add_command(inner)  # *

if __name__ == '__main__':
    cli()
Sign up to request clarification or add additional context in comments.

1 Comment

Actually, there's a simpler way: See click.palletsprojects.com/en/8.0.x/advanced/…

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.