Reputation: 31
I have number of configuration named arguments for my script. I want to provide them from configuration file and/or command line.
Have been using set_defaults() before, but this raises error when using add_arguments(..., required=True)
.
Is there better and working solution for Python 2.7?
from argparse import ArgumentParser
# this dict will be read from configuration file
config_args = {'my_argument': 'value from configuration file'}
parser = ArgumentParser()
parser.add_argument('--my-argument', dest='my_argument', required=True)
# use values from configuration file by default
parser.set_defaults(**config_args)
# override with command line arguments when provided
args = parser.parse_args({})
This example will cause error:
usage: [-h] --required REQUIRED
: error: argument --required is required
Upvotes: 0
Views: 3739
Reputation: 31
Based on @hpaulj answer, made slight adjustment for the code. Using ArgumentParser to notify about missing arguments whether provided from command line or config file.
Python27 ArgumentParser does not provide public API for accessing added argument actions. But there is private property _actions
. So simply looping over parser._actions
and resetting required
property if provided from config file is sufficient solution for my use case.
Here is the fixed code:
from argparse import ArgumentParser
# this dict will be read from configuration file
config_args = {'my_argument': 'value from configuration file'}
parser = ArgumentParser()
arg = parser.add_argument('--my-argument', required=True)
# use values from configuration file by default
parser.set_defaults(**config_args)
# Reset `required` attribute when provided from config file
for action in parser._actions:
if action.dest in config_args:
action.required = False
# override with command line arguments when provided
args = parser.parse_args({})
Upvotes: 3
Reputation: 231595
In [355]: config_args = {'my_argument': 'value from configuration file'}
In [356]: parser = argparse.ArgumentParser()
add_argument
creates and returns an Action
object. Let's save a reference to that object, and look at it:
In [357]: a1 = parser.add_argument('--my-argument', dest='my_argument', required
...: =True)
In [358]: a1
Out[358]: _StoreAction(option_strings=['--my-argument'], dest='my_argument', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
Notice that its default
is None
, the default default for store
. Also required
is set to True
.
set_defaults
changes the a1.default
value:
In [359]: parser.set_defaults(**config_args)
In [360]: a1
Out[360]: _StoreAction(option_strings=['--my-argument'], dest='my_argument', nargs=None, const=None, default='value from configuration file', type=None, choices=None, help=None, metavar=None)
set_defaults
can also be used to set a value for a dest
that doesn't appear in the arguments (illustrated in the docs for subparsers).
Run without arguments, we get an error - because the action is required. The presence of a default does not override that.
In [361]: parser.parse_args([])
usage: ipython3 [-h] --my-argument MY_ARGUMENT
ipython3: error: the following arguments are required: --my-argument
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2
If we change the required
attribute to False
(a1
is an object whose attributes can, within limits, be changed. That includes a1.default
).
In [362]: a1.required
Out[362]: True
In [363]: a1.required=False
In [364]: parser.parse_args([])
Out[364]: Namespace(my_argument='value from configuration file')
Your config value now appears. Commandline values can overwrite it.
You can also provide defaults in a namespace
parameter:
In [365]: ns = argparse.Namespace(my_argument='foo')
In [366]: parser.parse_args([], namespace=ns)
Out[366]: Namespace(my_argument='foo')
This foo
value has priority, so the action default or set_defaults
value isn't used.
https://bugs.python.org/issue29670 argparse: does not respect required args pre-populated into namespace
In this bug/issue the user wanted the presence of a value in ns
to override the required
test. That is, he want this ns
to act as though the value had been provided on the commandline. My conclusion is that it isn't any easy thing to change. The current parse_args
structure does not let us modify or bypass test for things like required
.
If you want fancier testing, I'd suggest leaving the default=None
, and do your own testing after parsing.
if args.my_argument is None:
args.my_argument = 'default from config'
Upvotes: 1