Making argsparse subparsers optional - python

So I have the following code:
parser = argparse.ArgumentParser(description='Manages anime_list.json', add_help=True, version='0.1')
parser.add_argument('-l', '--list', action='store_true', help='List all anime in file')
subparser = parser.add_subparsers(title='Actions', description='Actions that can be performed', dest='command')
add = subparser.add_parser('=add', help='Add anime entry')
add.add_argument('-n', '--name', type=str, required=False, default='',
help='The name of the anime adding. Will be auto filled in if left blank')
add.add_argument('-e', '--episode', type=int, required=False, default=0,
help='The last watched episode. Download starts add +1 this episode')
add.add_argument('-u', '--url', type=str, required=True, help='A link to any episode for the anime')
remove = subparser.add_parser('=remove', help='Remove anime entry')
remove.add_argument('-n', '--name', type=str, required=True, default='',
help='The name of the anime to remove')
args = parser.parse_args()
What I want is for the subparsers to be optional. When the user uses the --list argument, the subparsers arguments should not have to be supplied. When using argsparse's -h or -v options the parsing completes and the help information or version number is shown. But when just using my own -l it throws an exception saying that not enough arguments have been supplied.
I found a suggestion saying that using subparser.required = False should make them optional but it does not work.
Any idea how I can do this? I have looked up on this and can't seem to find a solution.

So I have found a solution, it's not optimal in my opinion but it works.
Thanks to Matthew in this answer.
Modifying the code like follows give me the functionality I want.
parser = argparse.ArgumentParser(description='Manages anime_list.json', add_help=True, version='0.1')
parser.add_argument('-l', '--list', action='store_true', help='List all anime in file')
args, sub_commands = parser.parse_known_args()
if args.list:
print 'Doing list'
else:
subparser = parser.add_subparsers(title='Actions', description='Actions that can be performed', dest='command')
add = subparser.add_parser('=add', help='Add anime entry')
add.add_argument('-n', '--name', type=str, required=False, default='',
help='The name of the anime adding. Will be auto filled in if left blank')
add.add_argument('-e', '--episode', type=int, required=False, default=0,
help='The last watched episode. Download starts add +1 this episode')
add.add_argument('-u', '--url', type=str, required=True, help='A link to any episode for the anime')
remove = subparser.add_parser('=remove', help='Remove anime entry')
remove.add_argument('-n', '--name', type=str, required=True, default='',
help='The name of the anime to remove')
args = parser.parse_args()
print args
return args
Basically parse the known arguments, in this case it would be the -l one. If the -l argument was not supplied, add the required subparsers and parse the arguments again.
If it is done this way, your --help will not work anymore as it will not show the subparsers' help text. You will have to create a manual help function.

Throw in a default parameter. For example:
parser.add_argument('-l', '--list', action='store_true', help='List all anime in file', default=False)
This will store a default value of False, and only when the -l flag is provided will the list variable be True.

I use the following solution, I find it quite clean, and it helps me to extend and make more specific my command line applications.
1) Define a main common and standard argparse with parent and subparsers. I do this in the main library or in a common class that I use in similar but different projects.
2) Add a subparser to escape the common argparse, something like "other" or something similar.
3) In the specific applications create a second argparser that is used when the first argument is "other".
example:
let's say I want to always use/inherit some common subparsers and parent but then also add some application specific parsers. Say the standard common parser has the following subparsers: load, query, show, ..., other.
then it will work with:
python code.py load -arg1 -arg2 ...
python code.py show -arga -argb ...
while, when using "other" it will take the parent args and do nothing, so that other case specific parsers can be used.
python code.py other application_specific_command -arg3 -arg4
clearly this does not make the subparser optional, so it is not a direct answer; more a possible alternative that works in some cases. I think it offers some advantages:
1) logic implementation without many if or try 2) can pass the parent args to the application specific parser (if "other" has parents) 3) allows some common args for "other", i.e. for all the case specific commands 4) maintains args helps

Related

Python argparse with Generic Subparser Commands

I have a python script that I want to use as a wrapper for another commandline tool. I want to intercept any subcommands that I have defined, but pass through all other subcommands and arguments. I have tried using a subparser, which seems ideal, but it doesn't seem to allow accepting a generic undefined command, something similar to what parse_known_args does for a regular ArgumentParser.
What I currently have:
ap = argparse.ArgumentParser()
subparsers = ap.add_subparsers(
title="My Subparser",
)
upload_parser = subparsers.add_parser('upload', help='upload help')
upload_parser.add_argument(
'path',
help="Path to file for upload"
)
upload_parser.add_argument(
'--recursive',
'-r',
action='store_true',
)
What I would like to add:
generic_parser = subparser.add_parser('*', help='generic help') # "*" to indicate any other value
generic_parser.add_argument(
'args',
nargs='*',
help='generic command arguments for passthru'
)
This does not work, as it simply expects either upload or a literal asterisk *.
More precisely, I want there to be a subcommand, I just don't know before hand what all the subcommands will be (or really I don't want to list out every subcommand of the tool I'm trying to wrap).
Upon further thought I have realized that this approach is somewhat flawed for my use in a few ways, though I think that this functionality might have its uses elsewhere, so I will leave the question up.
For my case, there is a conflict between seeing the help for my tool versus the one it wraps. That is, I can't distinguish between when the user wants to see the help for the wrapper or see the help for the tool it wraps.
I think you can try Click, this is really powerful and easy to use!
just check this example
import click
#click.command()
#click.option('--count', default=1, help='Number of greetings.')
#click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()

Is there a way to pass list as one of the arguments in argparse

I'm writing PySpark jobs for which I want to pass several arguments. One of them should have list of strings and also one list as well. I'm having trouble figuring out how to pass list to the list of argument.
parser = argparse.ArgumentParser(description='My pyspark job arguments')
parser.add_argument('--job', type=str, required=True, dest='job_name', help='The name of the spark job you want to run')
parser.add_argument('--res-path', type=str, required=True, dest='res_path',help='Path to the jobs resurces')
parser.add_argument('-l', '--list', action='append', required=True, help='List of list')
args = parser.parse_args()
When I pass values in CLI:
--list currency
--list RSD EUR
I want output of --list to look like this:
['currency', ['RSD', 'EUR']]
nargs lets you do something close:
parser.add_argument('--list', action='append', nargs='+')
will produce [['currency'], ['RSD', 'EUR']]. One drawback is that no positional arguments can be used immediate after --list, as they will be consumed as arguments of --list itself.

Nicer command line parse python

Using argparse, I have created a small script that contains a command line parser for my analysis program which is part of a self made python package. It works perfectly, but I don't really like how to control it.
This is how the code looks in the script itself
def myAnalysis():
parser = argparse.ArgumentParser(description='''
lala''')
parser.add_argument('-d', '--data',help='')
parser.add_argument('-e', '--option_1', help='', default=False, required=False)
parser.add_argument('-f', '--option_2', help='', default=False, required=False)
# combine parsed arguments
args = parser.parse_args()code here
Additional to this there is some more in the setup file of the analysis package
entry_points={
'console_scripts': [
'py_analysis = edit.__main__:myAnalysis'
]
As I said, this works without any problems. To analyze some data I have to use
py_analysis --data path_to_data_file
Sometimes, I need some of the options. For this it may look loke
py_analysis --data path_to_data_file --option_1 True --option_2 True
In my personal taste, this is kind of ugly. I would prefer something like
py_analysis path_to_data_file --option_1 --option_2
I am pretty sure this is possible. I just don't know how
Use store_true action
parser.add_argument('-e', '--option_1', help='', default=False, action ='store_true')
Then just adding to command line --option_1 will set its value to True.
To have a positional argument instead of an option, replace:
parser.add_argument('-d', '--data',help='')
by:
parser.add_argument('data_file', help='')

argparse and TypeError: execv() arg 2 must contain only strings

I know this one has been asked quite a bit, however trawling through the answers given either:
Have not resolved my issue
Don't really relate to what I am seeing
I am relatively new to coding in general and this is the first time I have tried to use argparse.
Essentially I am getting the following error from my script:
TypeError: execv() arg 2 must contain only strings
I am attempting to write a pretty simple wrapper for Spike Fuzzer, when I have the arguments -p, -v and -s statically set to an integer (not using argparse), the script runs without issue. When I attempt to use the arguments for them, I see the above error.
Please see code below:
import argparse, subprocess, os, sys
parser = argparse.ArgumentParser(prog='fuzz.py',
usage='%(prog)s -i 127.0.0.1 -p 21 -d /root/example -v 0 -s 0',
)
parser.add_argument('--version', action='version', version='[+] Version: BETA')
parser.add_argument('-i', metavar='127.0.0.1',
help="IP Address"
)
parser.add_argument('-p',
type=int,
default=21,
metavar='21',
nargs=1,
help="Port (Default 21)",
)
parser.add_argument('-d', metavar='/root/example',
help="Spike script directory"
)
parser.add_argument('-f', metavar='example.spk',
nargs=1,
help="Spike File"
)
parser.add_argument('-v',
type=int,
default=0,
metavar='0',
nargs=1,
help="SKIPVAR (Default 0)",
)
parser.add_argument('-s',
type=int,
default=0,
metavar='0',
nargs=1,
help="SKIPSTR (Default 0)",
)
args = parser.parse_args()
def Directory():
for file in os.listdir(args.d):
if file.endswith(".spk"):
print file
fuzzer = subprocess.call(['generic_send_tcp', args.i, args.p, file, args.v, args.s])
Directory()
If you would like any more information please give me a yell and I will provide what I can.
Cheers,
AJ
With regard to the argparse use, the only glaring issue is the use of nargs=1. With that the values will a one element list, not a string. Leave that off (or use the default nargs=None), and values will be strings.
Print args after parsing to have a clear idea of what has been parsed. And if need print selected values, eg. args.v.
Print this line:
['generic_send_tcp', args.i, args.p, file, args.v, args.s]
I'm guessing from the error that args.p is ['21'] when it should be simply '21'.
The choice of metavar, names etc are just a matter of style, and don't affect the running.
In [360]: p=argparse.ArgumentParser()
In [361]: p.add_argument('-f',nargs=1)
Out[361]: _StoreAction(option_strings=['-f'], dest='f', nargs=1, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [362]: p.parse_args(['-f','12'])
Out[362]: Namespace(f=['12'])

python argparse: 'Namespace' error

I'm trying to build a process with some parsing option, some mandatory and others optinal.
I have a problem with the following code:
bandlist=[1,2,3,4,5,6,7,8]
process=['rad', 'ref', 'radref']
sensors=['LC', 'LO', 'LE', 'LT']
series=['4', '5', '7', '8']
usage = "usage: %prog [options] "
parser = argparse.ArgumentParser(usage=usage)
parser.add_argument('-d', '--directory', dest='directory', action='store', type=str, \
help='complete path of landsat product folder: mydir/filename/')
parser.add_argument('-p', '--process', dest='operation', action='store', choices = process, \
help='process requested: radiance, reflectance, both', default='rad')
parser.add_argument('-l', '--series', dest='satellite', action='store', choices = series , \
help='Landsat series:4, 5, 7, 8')
parser.add_argument('-s', '--sensor', dest='sensor', action='store', choices = sensors, \
help='sensor acronymous, for example LO for Landsat OLI, or LE for Landsat ETM+, etc..', default=None)
parser.add_argument('-o', '--output', dest='output', type=str, \
help='Directory of output raster. \n \
Unless specified, output directory will be workDirectory/results/datafolder/. \n \
If specified, the output directory wil be mydirectory/results/filename/rad (and/or ref)/', default=None)
parser.add_argument('-x', action='store_true', dest='bool', help='activate iterative radiance and/or reflectance computation for all the bands', default=False)
parser.add_argument('-b', '--bands', dest='bands', choices = bandlist, type=int, \
help='bands to process', nargs='*', default=None)
(options, args) = parser.parse_args()
and there is the following error:
Traceback (most recent call last):
File "C:\Users\lbernardi\Desktop\extract\LandsatTMroutine_RadiometricCorrection_1.0.py", line 1210, in <module>
main()
File "C:\Users\lbernardi\Desktop\extract\LandsatTMroutine_RadiometricCorrection_1.0.py", line 784, in main
(options, args) = parser.parse_args()
TypeError: 'Namespace' object is not iterable
I don't understand what the error is about.
Thank for your help
parse_args doesn't return two items, it returns one.
args = parser.parse_args()
The error was issued by the interpreter while performing the (options, args) = ... assignment. The parser returned one object, a argparse.Namespace. But the assignment tries to split it into two items, e.g.
(options, args) = (args[0], args[1])
But the Namespace class definition does not implement a list or tuple like iteration. A custom Namespace class could, in theory, do so.
That's the technical detail behind the error. The practical issue is that argparse differs from optparse.
From the end of the argparse docs:
Replace (options, args) = parser.parse_args() with args = parser.parse_args() and add additional ArgumentParser.add_argument() calls for the positional arguments. Keep in mind that what was previously called options, now in argparse context is called args.
optparse processes all the flagged strings, and puts their values in an options object (I forget its exact nature). Strings that it can't process are returned as a list as the 2nd return value.
argparse expects you to define all arguments, both the flagged ones (called 'optionals') and unflagged ones (called 'positionals'). So values that in optparse would appear in the args list, appear under their own 'name/dest' in the argparse namespace.
There is another way of calling the argparse parser, parser.parse_known_args that behaves more like optparse. Strings it can't handle are returned in an extras list.
You mention that some of your arguments are required and some are not. At a first glance, your code makes everything 'optional'. That is, if you don't include the relevant flag in the commandline, that argument will get its default value.
One of your arguments uses nargs='*'. If that isn't specified the default will be None or [] (I forget the details).
You can also specify required=True parameter which makes an 'optional' required. Sorry about the confusing terminology. In that case, the parser will raise an error if you don't supply the flag in the commandline.
I didn't look much at your previous optparse question to see whether you expected an values in the args variable. In argparse usage those are 'positionals' and are required (unless their nargs makes them 'optional').
So while the simple fix is just use args = parser.parse_args(), I suspect there's more under the surface.
A style point:
parser.add_argument('-d', '--directory', dest='directory', action='store', type=str, \
help='complete path of t product folder: mydir/filename/')
can be simplified to
parser.add_argument('-d', '--directory', \
help='complete path of landsat product folder: mydir/filename/')
If not given dest is infered from the first long flag string. store is the default action value, and str is the default type.
In argparse, type is a function, one that converts the input string to something else. int and float are the most common alternatives. str works because it does nothing - returns the same string it was given. argparse actually uses an identify lambda function as the default type (e.g. lambda x:x).

Categories

Resources