Search code examples
pythoncommand-line-interfacepython-click

How to get access to click context in an option decorator?


I am trying to create a cli application using Python click and I want to use a function I assigned to click.core.Context.obj as the callback function of an option. The function I use as a callback function is generated based on the input so I cannot import that function.

Here is the code I have:

The class will be assigned to the context:

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

    def func_a():
        pass

    def func_b():
        pass

The command:

import click


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


@click.command('command')
@click.option('-o', '--my-option', callback=ctx.obj.function) # I want to use the function here
@click.pass_context
def the_command(ctx, my_option):
    click.echo(my_option)

The callback func I used in the above code does not work. What is the correct way to call ctx.obj.function?


Solution

  • 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