'Python Click: Multiple Key Value Pair Arguments

I'm using the Python Click library for my command-line interface. I'd like to have a command that takes multiple key value pairs. I'm flexible on the api. For example

my_cli my_command FOO=1 BAR=2

or maybe

my_cli my_command FOO 1 BAR 2

or even

my_cli my_command {"FOO": 1, "BAR": 2}

Is there an easy way to do this with Click?



Solution 1:[1]

The simplest solution is basically the same thing you'd do with a regular Python function where you wanted an API like this.

Take a single parameter that groups the variable-length stream of arguments into a tuple. Then, what you do depends on whether you want separate arguments:

>>> def func(*args):
...     d = dict(zip(args[::2], args[1::2]))
...     print(d)
>>> func('FOO', 1, 'BAR', 2)
{'FOO': 1, 'BAR': 2}

… or combined key:value arguments:

>>> def func(*args):
...     d = dict(arg.split(':') for arg in args)
...     print(d)

This one is a bit hacky to use, because in Python, arguments aren't just space-separated words, but bear with me on that:

>>> func('FOO:1', 'BAR:2')
{'FOO': 1, 'BAR': 2}

The click equivalent for the first looks like this:

@click.command()
@click.argument('args', nargs=-1)
def my_command(args):
    d = dict(zip(args[::2], args[1::2]))
    click.echo(d)

(Obviously you can stick that in a click.group, etc., just like any other command.)

And now:

$ ./clicky.py FOO 1 BAR 2
{'FOO': 1, 'BAR': 2}

And the second looks like this:

@click.command()
@click.argument('args', nargs=-1)
def my_command(args):
    d = dict(arg.split(':') for arg in args)
    click.echo(d)

And notice that now, using it is not hacky at all, because to your shell, arguments are just words separated by spaces:

$ ./clicky.py FOO:1 BAR:2
{'FOO': 1, 'BAR': 2}

What if you want to handle both KEY=VALUE and KEY:VALUE? Then you just have to write something slightly more complicated than arg.split(':'). And you'll probably want some better error handling too. But that should be enough to get you started.

Solution 2:[2]

The same is possible with options instead of arguments, see Tuples as Multi Value Options in combination with Multiple Options.

import click

@click.command()
@click.option("--dict", "-d", "mydict", type=(str, int), multiple=True)
def cli(mydict):
    d = dict(mydict)
    click.echo(d)

if __name__ == "__main__":
    cli()

Example:

$ python3 ./clickexample.py -d hello 1 -d foo 2 -d baz 3
{'hello': 1, 'foo': 2, 'baz': 3}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 reox