Search code examples
pythonpython-click

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

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