UsageΒΆ

Example of an utility that can take settings from:

  • a set of default values
  • environment variables
  • config file
  • command line arguments
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import argparse
import json
import sys

import cfglib
from cfglib.sources.args import ArgsNamespaceConfig
from cfglib.sources.env import EnvConfig


# Config definitions
# These will normally be in a separate module,
# to be imported by the rest of the project like:
# from xyz.config import cfg
def parse_config_file(config_file_path) -> cfglib.Config:
    with open(config_file_path) as config_file:
        file_config = cfglib.DictConfig(json.load(config_file))
        return cfglib.ProjectedConfig(file_config, cfglib.UPPERCASE_PROJECTION)


class ExampleToolConfig(cfglib.SpecValidatedConfig):
    MESSAGE = cfglib.StringSetting(default='Hello!')
    CONFIG_FILE = cfglib.StringSetting(default=None)


cfg = ExampleToolConfig([
    EnvConfig(prefix='EXAMPLE_', lowercase=False),
])


# Main program
def parse_cmdline_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--config-file', type=str,
        required=False, default=cfglib.MISSING,
        help='Path to a json-encoded config file',
    )
    parser.add_argument(
        '--message', type=str,
        required=False, default=cfglib.MISSING,
        help='The message that will be printed',
    )

    return parser.parse_args()


def initialize_config():
    cmdline_config = ArgsNamespaceConfig(
        parse_cmdline_arguments(),
        uppercase=True,
    )
    # Command line arguments shoule be highest priority, so append them to the end
    cfg.subconfigs.append(cmdline_config)

    if cfg.get('config_file'):
        # Load configuration from a config file, if specified
        # Config file's priority is below that of cmdline args, so insert at position 1
        cfg.subconfigs.insert(1, parse_config_file(cfg['config_file']))

    # With the additional configuration sources injected, the end priority order
    # from lowest to highest priority is now:
    # - environment variables
    # - config file
    # - command line arguments

    cfg.validate()


def main():
    initialize_config()
    print(f'Config: {cfg}', file=sys.stderr)

    message = cfg.MESSAGE
    print(message)


if __name__ == '__main__':
    main()