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!
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?
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.
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.
By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.
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”
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.
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
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):
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'.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With