Last active
January 10, 2019 21:37
-
-
Save four43/52e72702fee922870ee2f4c1ffb11264 to your computer and use it in GitHub Desktop.
In Python, set default values for ArugmentParser, optionally read in a config file or override with CLI arguments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[defaults] | |
option2:Overridden #2 in config | |
option3:Overridden #3 in config | |
flag: 0 | |
number_int: 6 | |
number_float: 0.2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Thanks to: http://blog.vwelch.com/2011/04/combining-configparser-and-argparse.html | |
# Features: | |
# * Cascading overrides (defaults, then config file, then command line arguments) | |
# * bool/int/float parse correctly as long as we have a default set. | |
import argparse | |
import configparser | |
from typing import TypeVar, Callable, Generic, Dict | |
T = TypeVar('T') | |
class _ConfigType(Generic[T]): | |
def __init__(self, name: str, parse_func: Callable[[str, str], T]): | |
self.name = name | |
self.parse_func = parse_func | |
def parse_config_and_args(arg_parser: argparse.ArgumentParser): | |
# Add option for our config file path | |
arg_parser.add_argument("-c", "--config", help="Specify config file", metavar="FILE") | |
args, remaining_argv = arg_parser.parse_known_args() # Parse for config file path | |
if args.config: | |
config = configparser.ConfigParser() | |
config.read([args.config]) | |
first_section = config.sections()[0] # Just assume first section for this example. | |
config_values = dict(config.items(first_section)) | |
defaults = vars(arg_parser.parse_args([])) | |
merged_config_file = { | |
**defaults, | |
**config_values | |
} | |
# Try and parse out config with defaults | |
type_mapping: Dict[type, _ConfigType] = { | |
bool: _ConfigType[bool]("boolean", config.getboolean), | |
int: _ConfigType[int]("integer", config.getint), | |
float: _ConfigType[float]("float", config.getfloat) | |
} | |
for key, value in defaults.items(): | |
type_config = None | |
try: | |
type_config = type_mapping[type(value)] | |
merged_config_file[key] = type_config.parse_func(first_section, key) | |
except ValueError: | |
if isinstance(type_config, _ConfigType): | |
raise ValueError("\"%s\" in config wasn't set to a %s. Either omit or set to a valid " | |
"%s." % (key, type_config.name, type_config.name)) | |
else: | |
raise ValueError( | |
"\"%s\" wasn't the correct type. Please double check the type in the config." % key) | |
except KeyError: | |
pass | |
except configparser.NoOptionError: | |
pass | |
arg_parser.set_defaults(**merged_config_file) | |
return arg_parser.parse_args() # Parse our full list of CLI opts now. | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser( | |
description="Testing app to quickly check layering of config file + cli arguments." | |
) | |
parser.add_argument("--option1", default="opt1 default", help="some option help text") | |
parser.add_argument("--option2", default="opt2 default", help="some other option help text") | |
parser.add_argument("--option3", default="opt3 default", help="some other option") | |
parser.add_argument("--no-default", help="No default") | |
parser.add_argument("--flag", default=False, action="store_true") # A boolean, True if flag present. | |
parser.add_argument("--number-int", dest="number_int", type=int, default=42, help="Ensure we have an int") | |
parser.add_argument("--number-float", dest="number_float", type=float, default=0.5, | |
help="Ensure we have a float") | |
args = parse_config_and_args(parser) | |
print(args) | |
# Tests: | |
# $ ./arg_test.py | |
# > Namespace(config=None, flag=False, no_default=None, number_float=0.5, number_int=42, option1='opt1 default', option2='opt2 default', option3='opt3 default') | |
# $ ./arg_test.py -c ./app.ini --option3 "Hello World" | |
# > Namespace(config='./app.ini', flag=False, no_default=None, number_float=0.2, number_int=6, option1='opt1 default', option2='Overridden #2 in config', option3='Hello World') | |
# $ ./arg_test.py -c ./app.ini --option3 "Hello World" --flag --number-int 7 | |
# > Namespace(config='./app.ini', flag=True, no_default=None, number_float=0.2, number_int=7, option1='opt1 default', option2='Overridden #2 in config', option3='Hello World') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment