Is it possible to dynamically generate commands in Python Click
Asked Answered
E

2

7

I'm trying to generate click commands from a configuration file. Essentially, this pattern:

import click

@click.group()
def main():
    pass

commands = ['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:
  bar
  baz
  foo

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?

Elenaelenchus answered 5/11, 2018 at 2:25 Comment(0)
S
7

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():
    pass

commands = ['foo', 'bar', 'baz']

def bind_function(name, c):
    def func():
        print("I am the '{}' command".format(c))

    func.__name__ = name
    return func

for 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.

Stillage answered 5/11, 2018 at 9:30 Comment(0)
S
2

You should use function that creates another function for your command, here the example

import click

commands = ['foo', 'bar', 'baz']


@click.group()
def main():
    pass


def create_func(val):
    def f():
        print("I am the '{}' command".format(val))
    return f


for c in commands:
    main.command(name=c)(create_func(c))

if __name__ == '__main__':
    main()
Sandiesandifer answered 5/11, 2018 at 9:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.