Reputation: 1656
I have a Python script which takes a lot of arguments.
I currently use a configuration.ini
file (read using configparser
), but would like to allow the user to override specific arguments using command line.
If I'd only have had two arguments I'd have used something like:
if not arg1:
arg1 = config[section]['arg1']
But I don't want to do that for 30 arguments.
Any easy way to take optional arguments from cmd line, and default to the config
file?
Upvotes: 7
Views: 5785
Reputation: 4713
You can use parser.set_defaults()
to do a bulk override of defaults (so that non-entered arguments get populated from the config). Conveniently, this allows the argparse argument default field to specify a last-resort default for the case where the argument was not provided on the commandline nor in the config. However, the arguments still need to be added to the parser somehow so that the parser is willing to recognize them. Mostly, set_defaults()
is useful if you already have an argparse parser set up, but you just want to override the defaults to come from the config if they aren't specified on the commandline:
import argparse
config = dict(
a=11,
b=13,
c=19
)
parser = argparse.ArgumentParser()
# add arguments to parser ...
parser.add_argument('-a', type=int)
parser.add_argument('-b', type=int)
parser.add_argument('-c', type=int)
parser.set_defaults(**config)
args = parser.parse_args()
print(args)
If you weren't planning on already having a parser set up with all available parameters, then you could alternatively use your config to set one up (given the defaults directly for each argument, so no need to do the additional set_defaults()
step:
import argparse
parser = argparse.ArgumentParser()
config = dict(
a=11,
b=13,
c=19
)
for key, value in config.items():
parser.add_argument(f'-{key}', default=value, type=type(value))
args = parser.parse_args()
print(args)
Upvotes: 1
Reputation: 24280
You can use a ChainMap from the collections
module.
From the doc:
A ChainMap groups multiple dicts or other mappings together to create a single, updateable view. [...]
Lookups search the underlying mappings successively until a key is found. [...]
So, you could create
config
dict containing the key-value pairs from your config file,cmd_line_args
dict containing the ones given on the command lineThen, create a ChainMap:
from collections import ChainMap
combined = ChainMap(cmd_line_args, config)
When you access combined['arg1']
, arg1 will first be looked up in the cmd_line_args
dict, and if it isn't found there, config[arg1]
will be returned.
You can chain as many dicts as you wish, which lets you combine as many levels of defaults as you wish.
Upvotes: 6
Reputation: 19816
Try the following, using dict.update():
import argparse
import configparser
config = configparser.ConfigParser()
config.read('config.ini')
defaults = config['default']
parser = argparse.ArgumentParser()
parser.add_argument('-a', dest='arg1')
parser.add_argument('-b', dest='arg2')
parser.add_argument('-c', dest='arg3')
args = vars(parser.parse_args())
result = dict(defaults)
result.update({k: v for k, v in args.items() if v is not None}) # Update if v is not None
With this example of ini file:
[default]
arg1=val1
arg2=val2
arg3=val3
and
python myargparser.py -a "test"
result
would contain:
{'arg1': 'test', 'arg2': 'val2', 'arg3': 'val3'}
Upvotes: 8