nargs depending on another setting? - python

I'm trying to write a program that supports arbitrary bitwise opertions: AND, OR, NOT and COUNT for bitmaps. The usage is that you run program.py --and f1.bit f2.bit and it prints you the result to the stdout.
The problem is that I'd like the parser to handle all the caveats. Specifically, I'd like the nargs to depend on the mode that's set - if it's set to COUNT or NOT, exactly one file is expected, if it's set to OR or AND, expect exactly two. Here's some (non-working) example code:
#!/usr/bin/env python
import argparse
def main(mode, fnames):
pass
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-O', '--or',
nargs=2,
action='store_const', const='or'
)
args = parser.parse_args()
import pprint
pprint.pprint(args.__dict__)
#main(**args.__dict__)
And the error I'm getting:
Traceback (most recent call last):
File "bitmaptool.py", line 12, in <module>
action='store_const', const='or'
File "/usr/lib/python3.7/argparse.py", line 1362, in add_argument
action = action_class(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'nargs'
Commenting out nargs helps, as does leaving nargs out but commenting out action - but I want both. Do I need to implement it manually or is there a trick or another library that would let me get there?
EDIT I wanted to clarify what I'm looking for by showing what code I needed to write manually for the thing to work:
if __name__ == '__main__':
parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
parser.add_argument('-O', '--or', nargs=2)
parser.add_argument('-A', '--and', nargs=2)
parser.add_argument('-M', '--minus', nargs=2)
parser.add_argument('-C', '--count', nargs=1)
parser.add_argument('-N', '--not', nargs=1)
parser.add_argument('-o', '--output', default='/dev/stdout')
args = parser.parse_args().__dict__
mode = None
files = []
for current_mode in ['or', 'and', 'not', 'count']:
if current_mode in args:
if mode is not None:
sys.exit('ERROR: more than one mode was specified')
mode = current_mode
files = args[mode]
if mode is None:
sys.stderr.write('ERROR: no mode was specified\n\n')
parser.print_help()
sys.exit(1)
import pprint
pprint.pprint(args)
Is there a more elegant way to get there?

store_const never gets arguments, it literally stores what you stated as const or None. Because it's a constant, not a variable. From the argparse's action documentation, ephasis mine:
'store_const' - This stores the value specified by the const keyword argument. The 'store_const' action is most commonly used with optional arguments that specify some sort of flag.
You should change the action to something that will actually store the filenames passed. As per argparse's nargs documentation and example, you actually don't need to specify action at all, default (action='store') will suffice.
Example from documentation:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', nargs=2) #this line
>>> parser.add_argument('bar', nargs=1)
>>> parser.parse_args('c --foo a b'.split())
Namespace(bar=['c'], foo=['a', 'b'])
EDIT for the edited version of the question - mutually exclusive group will make sure only one argument (from that group, of course) is specified:
if __name__ == '__main__':
parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
parser.add_argument('-o', '--output', default='/dev/stdout')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-O', '--or', nargs=2)
group.add_argument('-A', '--and', nargs=2)
group.add_argument('-M', '--minus', nargs=2)
group.add_argument('-C', '--count', nargs=1)
group.add_argument('-N', '--not', nargs=1)
args = parser.parse_args().__dict__
import pprint
pprint.pprint(args)

Related

python argparse default with nargs wont work [duplicate]

This question already has answers here:
Argparse optional argument with different default if specified without a value
(2 answers)
Closed last month.
Here is my code:
from argparse import ArgumentParser, RawTextHelpFormatter
example_text = "test"
parser = ArgumentParser(description='my script.',
epilog=example_text,
formatter_class=RawTextHelpFormatter)
parser.add_argument('host', type=str, default="10.10.10.10",
help="Device IP address or Hostname.")
parser.add_argument('-j','--json_output', type=str, default="s", nargs='?',choices=["s", "l"],
help="Print GET statement in json form.")
#mutally exclusive required settings supplying the key
settingsgroup = parser.add_mutually_exclusive_group(required=True)
settingsgroup.add_argument('-k', '--key', type=str,
help="the api-key to use. WARNING take care when using this, the key specified will be in the user's history.")
settingsgroup.add_argument('--config', type=str,
help="yaml config file. All parameters can be placed in the yaml file. Parameters provided from form command line will take priority.")
args = parser.parse_args()
print(args.json_output)
my output:
None
Everything I am reading online says this should work, but it doesn't. Why?
You could use the const= parameter. const stores its value when the option is present but have no values
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-j', '--json-output', nargs='?', choices=['s', 'l'], const='s')
args = parser.parse_args()
However design wise, it might be better to use the following:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output-type', choices=['json-s', 'json-l', 'normal'], default='normal')
args = parser.parse_args()

Argparse Unable to access optional argument with specific name [duplicate]

I want to have some options in argparse module such as --pm-export however when I try to use it like args.pm-export I get the error that there is not attribute pm. How can I get around this issue? Is it possible to have - in command line options?
As indicated in the argparse docs:
For optional argument actions, the value of dest is normally inferred from the option strings. ArgumentParser generates the value of dest by taking the first long option string and stripping away the initial -- string. Any internal - characters will be converted to _ characters to make sure the string is a valid attribute name
So you should be using args.pm_export.
Unfortunately, dash-to-underscore replacement doesn't work for positional arguments (not prefixed by --).
E.g:
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('logs-dir',
help='Directory with .log and .log.gz files')
parser.add_argument('results-csv', type=argparse.FileType('w'),
default=sys.stdout,
help='Output .csv filename')
args = parser.parse_args()
print args
# gives
# Namespace(logs-dir='./', results-csv=<open file 'lool.csv', mode 'w' at 0x9020650>)
So, you should use 1'st argument to add_argument() as attribute name and metavar kwarg to set how it should look in help:
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('logs_dir', metavar='logs-dir',
nargs=1,
help='Directory with .log and .log.gz files')
parser.add_argument('results_csv', metavar='results-csv',
nargs=1,
type=argparse.FileType('w'),
default=sys.stdout,
help='Output .csv filename')
args = parser.parse_args()
print args
# gives
# Namespace(logs_dir=['./'], results_csv=[<open file 'lool.csv', mode 'w' at 0xb71385f8>])
Dashes are converted to underscores:
import argparse
pa = argparse.ArgumentParser()
pa.add_argument('--foo-bar')
args = pa.parse_args(['--foo-bar', '24'])
print args # Namespace(foo_bar='24')
Concise and explicit but probably not always acceptable way would be to use vars():
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('a-b')
args = vars(parser.parse_args())
print(args['a-b'])
getattr(args, 'positional-arg')
This is another OK workaround for positional arguments:
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('a-b')
args = parser.parse_args(['123'])
assert getattr(args, 'a-b') == '123'
Tested on Python 3.8.2.
I guess the last option is to change shorten option -a to --a
import argparse
parser = argparse.ArgumentParser(description="Help")
parser.add_argument("--a", "--argument-option", metavar="", help="") # change here
args = parser.parse_args()
option = args.a # And here
print(option)

Passing main arguments after subparser arguments

Imagine you've got common arguments for several subparsers:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--learn_rate",
type=float,
)
subparsers = parser.add_subparsers(help='task', dest='lib')
spacy_parser = subparsers.add_parser(
"spacy",
)
args = vars(parser.parse_args())
From the command line, you have to run python test.py --learn_rate 2 spacy. Is it possible to make it so that python test.py spacy --learn_rate 2 also works?
learn_rate isn't an option common to your subparsers; it's an option on the main command, which is available to your code regardless of which subparser gets invoked. If you truly want to share an option among multiple subparsers as in your second use case, you need to define it in a parent parser.
import argparse
parser = argparse.ArgumentParser()
shared_parent = argparse.ArgumentParser(add_help=False)
shared_parent.add_argument(
"--learn_rate",
type=float,
)
subparsers = parser.add_subparsers(help='task', dest='lib')
spacy_parser = subparsers.add_parser(
"spacy",
parents=[shared_parent]
)
args = vars(parser.parse_args())
Allowing --learn_rate to be used in either position is trickier. While you could define the shared_parent parser first, then add it to the main parser as well with parser = argparse.ArgumentParser(parents=[shared_parent]), the subcommand will overwrite whatever value you specify from the main parser with the default if you don't use the option from the subparser. Working around this behavior of argparse would probably require a custom action at the very least.
This works:
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
subparsers = parser.add_subparsers(help='task', dest='lib')
spacy_parser = subparsers.add_parser(
"spacy",
help="Spacy's Textcat",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
ulmfit_parser = subparsers.add_parser(
"ulmfit",
help="Fastai's ULMFiT",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
for subparser in subparsers.choices.values():
subparser.add_argument(...)

Unable to assign default values to arguments in Python argparse

I am using Python argparse to take in parameters through the CLI. I have tried using the following but when I don't give any one of the arguments, it gives the output as None. I want then default ones to be the ones provided in const=. Please take a look.
parser = argparse.ArgumentParser()
parser.add_argument('--input', nargs='?', const='testInput')
parser.add_argument('--target', nargs='?', const='testTarget')
parser.add_argument('--msg', nargs='?', const='helloFromTheOtherSide')
args = parser.parse_args()
print args.input
If I don't give input, it prints it as None as I said. I want it to print TestInput instead..
Use the default argument:
parser = argparse.ArgumentParser()
parser.add_argument('--input', nargs='?', default='testInput')
parser.add_argument('--target', nargs='?', default='testTarget')
parser.add_argument('--msg', nargs='?', default='helloFromTheOtherSide')
args = parser.parse_args()
print args.input
With
parser.add_argument('--input', nargs='?', default='testInput', const='aConst')
you have a 3 way choice
prog # input='testInput'
prog --input # input='aConst'
prog --input myfile # input='myfile'
If you don't need that aConst option, omit the nargs='?'. Since it is a flagged argument it is already optional. It doesn't need the `?'.
parser.add_argument('--input', default='testInput')

argparse module How to add option without any argument?

I have created a script using argparse.
The script needs to take a configuration file name as an option, and user can specify whether they need to proceed totally the script or only simulate it.
The args to be passed: ./script -f config_file -s or ./script -f config_file.
It's ok for the -f config_file part, but It keeps asking me for arguments for the -s which is optionnal and should not be followed by any.
I have tried this:
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--file')
#parser.add_argument('-s', '--simulate', nargs = '0')
args = parser.parse_args()
if args.file:
config_file = args.file
if args.set_in_prod:
simulate = True
else:
pass
With the following errors:
File "/usr/local/lib/python2.6/dist-packages/argparse.py", line 2169, in _get_nargs_pattern
nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
TypeError: can't multiply sequence by non-int of type 'str'
And same errror with '' instead of 0.
As #Felix Kling suggested use action='store_true':
>>> from argparse import ArgumentParser
>>> p = ArgumentParser()
>>> _ = p.add_argument('-f', '--foo', action='store_true')
>>> args = p.parse_args()
>>> args.foo
False
>>> args = p.parse_args(['-f'])
>>> args.foo
True
To create an option that needs no value, set the action [docs] of it to 'store_const', 'store_true' or 'store_false'.
Example:
parser.add_argument('-s', '--simulate', action='store_true')

Categories

Resources