Search code examples
pythoncommand-line-argumentsstdoutpipingpython-click

Call another click command from a click command


I want to use some useful functions as commands. For that I am testing the click library. I defined my three original functions then decorated as click.command:

import click
import os, sys

@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=True)
def add_name(content, to_stdout=False):
    if not content:
        content = ''.join(sys.stdin.readlines())
    result = content + "\n\tadded name"
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result


@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=True)
def add_surname(content, to_stdout=False):
    if not content:
        content = ''.join(sys.stdin.readlines())
    result = content + "\n\tadded surname"
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result

@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=False)
def add_name_and_surname(content, to_stdout=False):
    result = add_surname(add_name(content))
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result

This way I am able to generate the three commands add_name, add_surname and add_name_and_surname using a setup.py file and pip install --editable . Then I am able to pipe:

$ echo "original content" | add_name | add_surname 
original content

    added name
    added surname

However there is one slight problem I need to solve, when composing with different click commands as functions:

$echo "original content" | add_name_and_surname 
Usage: add_name_and_surname [OPTIONS] [CONTENT]

Error: Got unexpected extra arguments (r i g i n a l   c o n t e n t 
)

I have no clue why it does not work, I need this add_name_and_surname command to call add_name and add_surname not as command but as functions, else it defeats my original purpose of using functions as both library functions and commands.


Solution

  • Due to the click decorators the functions can no longer be called just by specifying the arguments. The Context class is your friend here, specifically:

    1. Context.invoke() - invokes another command with the arguments you supply
    2. Context.forward() - fills in the arguments from the current command

    So your code for add_name_and_surname should look like:

    @click.command()
    @click.argument('content', required=False)
    @click.option('--to_stdout', default=False)
    @click.pass_context
    def add_name_and_surname(ctx, content, to_stdout=False):
        result = ctx.invoke(add_surname, content=ctx.forward(add_name))
        if to_stdout is True:
            sys.stdout.writelines(result)
        return result
    

    Reference: http://click.pocoo.org/6/advanced/#invoking-other-commands