Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

decorate on top of a click command

I am trying to decorate a function which is already decorated by @click and called from the command line.

Normal decoration to capitalise the input could look like this:

standard_decoration.py

def capitalise_input(f):
    def wrapper(*args):
        args = (args[0].upper(),)
        f(*args)
    return wrapper

@capitalise_input
def print_something(name):
    print(name)

if __name__ == '__main__':
    print_something("Hello")

Then from the command line:

$ python standard_decoration.py
HELLO

The first example from the click documentation looks like this:

hello.py

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

When run from the command line:

$ python hello.py --count=3
Your name: John
Hello John!
Hello John!
Hello John!
  1. What is the correct way to apply a decorator which modifies the inputs to this click decorated function, eg make it upper-case just like the one above?

  2. Once a function is decorated by click, would it be true to say that any positional arguments it has are transformed to keyword arguments? It seems that it matches things like '--count' with strings in the argument function and then the order in the decorated function no longer seems to matter.

like image 633
cardamom Avatar asked Apr 05 '18 16:04

cardamom


People also ask

What does click command() do?

Python click module is used to create command-line (CLI) applications. It is an easy-to-use alternative to the standard optparse and argparse modules. It allows arbitrary nesting of commands, automatic help page generation, and supports lazy loading of subcommands at runtime.

Which function is the decorator?

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

Which keyword is used for decorating a function?

Decorators can be chained A Decorator function is used only to format the output of another function dec keyword is used for decorating a function Decorators always return None”

What are decorators used for in Python?

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.


3 Answers

Harvey's answer won't work with command groups. Effectively this would replace the 'hello' command with 'wrapper' which is not what we want. Instead try something like:

from functools import wraps

def test_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        kwargs['name'] = kwargs['name'].upper()
        return f(*args, **kwargs)
    return wrapper
like image 160
MattK Avatar answered Oct 29 '22 06:10

MattK


It appears that click passes keywords arguments. This should work. I think it needs to be the first decorator, i.e. it is called after all of the click methods are done.

def capitalise_input(f):
    def wrapper(**kwargs):
        kwargs['name'] = kwargs['name'].upper()
        f(**kwargs)
    return wrapper


@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
@capitalise_input
def hello(count, name):
    ....

You could also try something like this to be specific about which parameter to capitalize:

def capitalise_input(key):
    def decorator(f):
        def wrapper(**kwargs):
            kwargs[key] = kwargs[key].upper()
            f(**kwargs)
        return wrapper
    return decorator

@capitalise_input('name')
def hello(count, name):
like image 4
Harvey Avatar answered Oct 29 '22 06:10

Harvey


About click command groups - we need to take into account what the documentation says - https://click.palletsprojects.com/en/7.x/commands/#decorating-commands

So in the end a simple decorator like this:

def sample_decorator(f):
    def run(*args, **kwargs):
        return f(*args, param="yea", **kwargs)
    return run

needs to be converted to work with click:

from functools import update_wrapper

def sample_decorator(f):
    @click.pass_context
    def run(ctx, *args, **kwargs):
        return ctx.invoke(f, *args, param="yea", **kwargs)
    return update_wrapper(run, f)

(The documentation suggests using ctx.invoke(f, ctx.obj, but that has led to an error of 'duplicite arguments'.)

like image 2
Holi Avatar answered Oct 29 '22 07:10

Holi