Problem

There is click module that allows you to create comman dline interfaces for your python scripts. The advantages of click are

  • nice syntax
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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()
  • straightforward argument parsing strategy (in comparison to the standard argparse )

Click doesn’t support config files by default. There is a number of additional modules that implement this feature.

Comparison

featureclick-configclick-configfileclick_config_fileSO snippet
Last commit in the repositoryMay 5, 2015Sep 24, 2017Jan 23, 2018-
Supports iniyesyesyeseasy to implement
Supports jsonnonoyes with strange extension techniqueeasy to implement
Supports yamlyesnoyes with strange extension techniqueyes
implementation notesclick-config provides a decorator that takes a python object and overwrites its attributes with values passed into the program via command line arguments.through context_settingsusable by simply adding the appropriate decoratorDefault values from click.option don’t work as expected. They override values from the file. One have to check for defaults manually. Not sure if environment variables work nicely.

Syntax examples

click-config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from __future__ import print_function
import click
import click_config


class config(object):
    class logger(object):
        level = 'INFO'

    class mysql(object):
        host = 'localhost'


@click.command()
@click_config.wrap(module=config, sections=('logger', 'mysql'))
def main():
    print('log level: {}, mysql host: {}'.format(
        config.logger.level,
        config.mysql.host
    ))


if __name__ == '__main__':
    main()

click-configure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# BASIC SOLUTION FOR: Command that uses one or more configuration files.
import click

CONTEXT_SETTINGS = dict(default_map=ConfigFileProcessor.read_config())

@click.command(context_settings=CONTEXT_SETTINGS)
@click.option("-n", "--number", "numbers", type=int, multiple=True)
@click.pass_context
def command_with_config(ctx, numbers):
    """Example for a command that uses an configuration file"""
    pass
    ...

if __name__ == "__main__":
    command_with_config()

click_config_file

1
2
3
4
5
@click.command()
@click.option('--name', default='World', help='Who to greet.')
@click_config_file.configuration_option()
def hello(name):
    click.echo('Hello {}!'.format(name))

Stack overflow snippet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Custom Class:

def CommandWithConfigFile(config_file_param_name):

    class CustomCommandClass(click.Command):

        def invoke(self, ctx):
            config_file = ctx.params[config_file_param_name]
            if config_file is not None:
                with open(config_file) as f:
                    config_data = yaml.load(f)
                    for param, value in ctx.params.items():
                        if value is None and param in config_data:
                            ctx.params[param] = config_data[param]

            return super(CustomCommandClass, self).invoke(ctx)

    return CustomCommandClass
Using Custom Class:
1
2
3
4
5
@click.command(cls=CommandWithConfigFile('config_file'))
@click.argument("arg")
@click.option("--opt")
@click.option("--config_file", type=click.Path())
def main(arg, opt, config_file):
Test Code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# !/usr/bin/env python
import click
import yaml

@click.command(cls=CommandWithConfigFile('config_file'))
@click.argument("arg")
@click.option("--opt")
@click.option("--config_file", type=click.Path())
def main(arg, opt, config_file):
    print("arg: {}".format(arg))
    print("opt: {}".format(opt))
    print("config_file: {}".format(config_file))


main('my_arg --config_file config_file'.split())

Test Results:

arg: my_arg
opt: my_opt
config_file: config_file