Very much late, but this might help others with the same issue: Click.Command has both a params property and an (undocumented) get_params(ctx) method. The latter method also returns the help option, if one exists.

To access the default values regardless of order of execution, use the following in your callback code:

defaults = {p.name: p.default for p in ctx.command.params}
# To use param values if they exist, uncomment the following line
# defaults.update(ctx.params)

Note that as tmwilson26 correctly noticed, only params which have already been processed by Click will appear in ctx.params.

Answer from Not Carlos Matos on Stack Overflow
🌐
Readthedocs
pocoo-click.readthedocs.io › en › latest › api
API - the Click Documentation
Because base commands do not implement a lot of the API that other parts of Click take for granted, they are not supported for all operations. For instance, they cannot be used with the decorators usually and they have no built-in callback system.
🌐
Click Documentation
click.palletsprojects.com › en › stable › advanced
Advanced Patterns — Click Documentation (8.5.x)
A callback is a function invoked with three parameters: the current Context, the current Parameter, and the value. The context provides some useful features such as quitting the application and gives access to other already processed parameters. ... def print_version(ctx, param, value): if ...
🌐
GitHub
github.com › pallets › click › issues › 1483
dynamically add Options to Command through callback of an eager option · Issue #1483 · pallets/click
March 1, 2020 - Example (not yet working) I posted a question on SO with a simplified code example of my try at implementing the behavior described in the title. I shall re-post it below for convenience: import click def std_cb(ctx, param, standardize):...
Author: pallets
🌐
Click Documentation
click.palletsprojects.com › api
API — Click Documentation (8.5.x)
A basic command handles command line parsing and might dispatch more parsing to commands nested below it. ... context_settings (MutableMapping[str, Any]) – an optional dictionary with defaults that are passed to the context object. callback (Callable[[...], Any] | None) – the callback to invoke.
Find elsewhere
🌐
Readthedocs
pocoo-click.readthedocs.io › en › latest › options
Options - the Click Documentation
A callback is a function that is invoked with two parameters: the current Context and the value. The context provides some useful features such as quitting the application and gives access to other already processed parameters. ... def print_version(ctx, param, value): if not value or ...
🌐
Click Documentation
click.palletsprojects.com › en › stable › commands
Advanced Groups and Context — Click Documentation (8.5.x)
Here’s an example of that, where each subcommand creates a function that processes the input, then the result callback calls each function. The command takes a file, processes each line, then outputs it. If no subcommands are given, it outputs the contents of the file unchanged. @click.group(chain=True, invoke_without_command=True) @click.argument("fin", type=click.File("r")) def cli(fin): pass @cli.result_callback() def process_pipeline(processors, fin): iterator = (x.rstrip("\r\n") for x in input) for processor in processors: iterator = processor(iterator) for item in iterator: click.echo(
Top answer
1 of 1
2

I would suggest adding a @staticmethod to your Config class to use as the target of the callback like:

Callback Method:

@staticmethod
def click_callback(ctx, param, value):
    getattr(ctx.obj, ctx.obj.func_name)()
    return value

Test Code:

import click

class Config:
    def __init__(self, func_name):
        self.func_name = func_name

    def func_a(self):
        click.echo('Func A')

    def func_b(self):
        click.echo('Func B')

    @staticmethod
    def click_callback(ctx, param, value):
        getattr(ctx.obj, ctx.obj.func_name)()
        return value


@click.group()
@click.option('-f', '--func-name', required=True)
@click.pass_context
def group(ctx, func_name):
    ctx.obj = Config(func_name)


@group.command()
@click.option('--my-option', '-o', callback=Config.click_callback)
def command(my_option):
    click.echo('command: {}'.format(my_option))


if __name__ == "__main__":
    commands = (
        '-f func_a command -o optionA',
        '-f func_b command -o optionB',
        '-f func_a command -o ',
        'command',
        '--help',
        '',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for cmd in commands:
        try:
            time.sleep(0.1)
            print('-----------')
            print('> ' + cmd)
            time.sleep(0.1)
            group(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

Results:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> -f func_a command -o optionA
Func A
command: optionA
-----------
> -f func_b command -o optionB
Func B
command: optionB
-----------
> -f func_a command -o 
Error: -o option requires an argument
-----------
> command
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Error: Missing option "-f" / "--func-name".
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  -f, --func-name TEXT  [required]
  --help                Show this message and exit.

Commands:
  command
-----------
> 
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  -f, --func-name TEXT  [required]
  --help                Show this message and exit.

Commands:
  command
🌐
Typer
typer.tiangolo.com › tutorial › options › callback-and-context
CLI Option Callback and Context - Typer
As it's based on standard Python types, it "just works". ✨ · You can get the typer.Context and the typer.CallbackParam simply by declaring a function parameter of each type.
🌐
Plotly
dash.plotly.com › vtk › click-hover
Click and Hover Callbacks | Plotly Dash
It’s possible to create callbacks based on user clicks and hovering. First, you need to specify the pickingModes prop in dash_vtk.View to be a list of modes you want to capture.
🌐
pytz
pythonhosted.org › dojson › _modules › click › core.html
click.core — DoJSON 1.3.1 documentation
:param callback: a callback that should be executed after the parameter was matched. This is called as ``fn(ctx, param, value)`` and needs to return the value. Before Click 2.0, the signature was ``(ctx, value)``. :param nargs: the number of arguments to match.
🌐
GitHub
github.com › pallets › click › issues › 950
Context.invoke doesn't run callback on the other command · Issue #950 · pallets/click
March 22, 2018 - When invoking a command using ctx.invoke(other_cmd, ...), I expected that the parameters passed would be validated per other_cmd's callback methods as declared on the arguments but that seems t...
Author: pallets
🌐
Stack Overflow
stackoverflow.com › questions › 60652345 › python-click-and-help-option-callback-isnt-eager
Python click and --help option callback isn't eager - Stack Overflow
I have the following click code: @click.group(invoke_without_command=True) def cli(): click.echo("Starting CallFlow....") setup_logging() # ##################----GEN---################## @...
Top answer
1 of 4
72

When you call add_name() and add_surname() directly from another function, you actually call the decorated versions of them so the arguments expected may not be as you defined them (see the answers to How to strip decorators from a function in python for some details on why).

I would suggest modifying your implementation so that you keep the original functions undecorated and create thin click-specific wrappers for them, for example:

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_name_command(content, to_stdout=False):
    return add_name(content, to_stdout)

You can then either call these functions directly or invoke them via a CLI wrapper script created by setup.py.

This might seem redundant but in fact is probably the right way to do it: one function represents your business logic, the other (the click command) is a "controller" exposing this logic via command line (there could be, for the sake of example, also a function exposing the same logic via a Web service for example).

In fact, I would even advise to put them in separate Python modules - Your "core" logic and a click-specific implementation which could be replaced for any other interface if needed.

2 of 4
67

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