Nicer command line parse python - 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='')

Related

passing multiple flags in argparse?

I am trying to pass multiple different flags using argparse. I know this kind of code would work for a single flag. if the -percentage flag is passed then do something
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-percentage', action='store_true')
but I'm trying to pass multiple flags, for example this code
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-serviceA', action='store_true')
parser.add_argument('-serviceB', action='store_true')
parser.add_argument('-serviceC', action='store_true')
parser.add_argument('-serviceD', action='store_true')
parser.add_argument('-activate', action='store_true')
and then pass flags -serivceB -activate, my intention is that the activate flag is basically a yes or no. and the service flag is the actual service. so that the service would get activated only when there is a activate flag next to it. how can I do this?
I hope i explained the situation in detail. please any help or tips are appreciated.
I was debugging it wrong. I spent last 5 hours trying to figure this out. Thanks to everyone who mentioned the comment!
for anyone experiencing the same issue, when you are debugging using a launch.json file. make sure your args are like this "args": [
"--serviceA", "--activate"
],
I had set up args like this "--serviceA --activate"

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()

Python 3.7 ArgumentParser.add_subparsers require positional before optionals

I'm trying to create a python script that will execute another script, depending on the first positional parameter. Think along the lines of how git add behaves.
Problem is that ArgumentParser appears to want the positional sub-command to be listed... at the end. Which is pretty counter-intuitive. (When you want to list all files, you do ls -a [FILE positional], not -a ls [FILE positional], so why would it require scriptname [optionals] subcommand instead of scriptname subcommand [optionals] since 'subcommand' is the 'real' command?)
Toy example:
def get_arg_parser():
parser = argparse.ArgumentParser()
# set up subprocessors
subparser = parser.add_subparsers(required=True)
parser.add_argument('--verbose', action='store_const', const=True, default=False, help="Enable verbose output.")
subcommand1_subparser = subparser.add_parser('subcommand1')
subcommand1_subparser.add_argument('--foo1', type=float)
subcommand2_subparser = subparser.add_parser('subcommand2')
subcommand2_subparser.add_argument('--foo2', type=float)
return parser
if __name__ == "__main__":
if len(sys.argv) > 1:
get_arg_parser().parse_args()
# more
else:
get_arg_parser().print_help()
Problem is that if I try to run python toyexample.py subcommand1 --verbose, it complains about error: unrecognized arguments: --verbose. Meanwhile, python toyexample.py --verbose subcommand1 works, but it's requiring the optionals before the name of the command you're actually intending to run.
How do I override this?
Thanks to #hpaulj, I found a solution: simply add the shared arguments to both subparsers.
I put the parser.add_argument('--verbose', action='store_const', const=True, default=False, help="Enable verbose output.") line in a add_shared_args_to_parser to function, which I then call twice, passing the subparsers.
Net result is that the subparsers have some unfortunate (but not terrible) duplication and the main parser has nothing but subparsers.

Making argsparse subparsers optional

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

How to use top-level arguments with subparsers in argparse

In Python's argparse, how do you implement top-level arguments while still using commands implemented as subparsers?
I'm trying to implement a --version argument to show the program's version number, but argparse is giving me error: too few arguments because I'm not specifying a sub-command for one of the subparsers.
My code:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
'-v', '--version',
help='Show version.',
action='store_true',
default=False
)
subparsers = parser.add_subparsers(
dest="command",
)
list_parser = subparsers.add_parser('list')
parser.parse_args(['--version'])
the output:
usage: myscript.py [-h] [-v] {list} ...
myscript.py: error: too few arguments
If you only need version to work, you can do this:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
'-v', '--version',
action='version',
version='%(prog)s 1.0',
)
Subparsers won't bother any more; the special version action is processed and exits the script before the parser looks for subcommands.
The subparsers is a kind of positional argument. So normally that's required (just as though you'd specified add_argument('foo')).
skyline's suggestion works because action='version' is an action class that exits after displaying its information, just like the default -h.
There is bug/feature in the latest argparse that makes subparsers optional. Depending on how that is resolved, it may be possible in the future to give the add_subparsers command a required=False parameter. But the intended design is that subparsers will be required, unless a flagged argument (like '-h') short circuits the parsing.

Categories

Resources