I'm trying to generate click
commands from a configuration file. Essentially, this pattern:
import click@click.group()
def main():passcommands = ['foo', 'bar', 'baz']
for c in commands:def _f():print("I am the '{}' command".format(c))_f = main.command(name=c)(_f)if __name__ == '__main__':main()
The --help
text looks good:
Usage: test_click.py [OPTIONS] COMMAND [ARGS]...Options:--help Show this message and exit.Commands:barbazfoo
However all of the commands seem to route to the last one that gets generated:
$ ./test_click.py foo
I am the 'baz' command
$ ./test_click.py bar
I am the 'baz' command
$ ./test_click.py baz
I am the 'baz' command
Is what I'm attempting actually possible?
The problem is with functional scope of python. When you create the function _f
it is using c
which has a higher scope than the function. So it will not be retained when you invoke the function. (_f
is invoked when you call main)
By the time you invoke main
the loop is complete and c will always hold the last value of the loop - which is baz
The solution is to attach the value c
to function using its own scope.
import click@click.group()
def main():passcommands = ['foo', 'bar', 'baz']def bind_function(name, c):def func():print("I am the '{}' command".format(c))func.__name__ = namereturn funcfor c in commands:f = bind_function('_f', c)_f = main.command(name=c)(f)if __name__ == '__main__':main()
The bind_function
also allows you to name your functions differently if required.