Display pydoc's description as part of argparse '--help' - python

I am using argparse.ArgumentParser() in my script, I would like to display the pydoc description of my script as part of the '--help' option of the argparse.
One possibly solution can be to use the formatter_class or the description attribute of ArgumentParser to configure the displaying of help. But in this case, we need to use the 'pydoc' command internally to fetch the description.
Do we have some other ways (possibly elegant) to do it?

You can retrieve the docstring of your script from the __doc__ global. To add it to your script's help, you can set the description argument of the parser.
"""My python script
Script to process a file
"""
p = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument('foo', help="Name of file to process")
p.parse_args()
Then the help will look like:
$ python tmp.py --help
usage: tmp.py [-h] foo
My python script
Script to process a file
positional arguments:
foo Name of file to process
optional arguments:
-h, --help show this help message and exit
You can use the epilog keyword argument instead of description to move the docstring to the end of the help, instead of immediately following the usage string.

There is an elegant argparse wrapper allowing to use a Python function docstring as a command help in your command line interface: dsargparse
It does this smartly keeping only the description part of the function docstring not the arguments part that can be irrelevant to your command.
As mentioned in its Readme:
dsargparse is a wrapper of argparse library which prepares helps and descriptions from docstrings. It also sets up functions to be run for each sub command, and provides a helper function which parses args and run a selected command.
Using this library, you don't need to write same texts in docstrings, help, and description.

Related

Looking to set a global default argument with argparse [duplicate]

This question already has answers here:
Display help message with Python argparse when script is called without any arguments
(18 answers)
Closed 3 years ago.
I am writting a new script and would like for the -h or --help argument to be called by default when the script is called without any parameters. So for example if someone calls command_line_utility.py then I want it to print the output you would get with command_line_utility.py -h.
I have dug around in the docs and looked at some examples, but all of them were specifying default argument values and not actually having arg parse call a default argument.
# Setting up Main Argument Parser
main_parser = argparse.ArgumentParser(description="A set of python web utility scripts")
main_parser.add_argument("-v",'--version', action='version', version='kuws V0.0.1')
# Setting up the main subparser
subparsers = main_parser.add_subparsers(help="Available commands found below, for more info on a command use: python command_line_utility.py <command> -h or kuws <command> -h")
"""Code below handles 'redirects' command in the main script
i.e. >python command_line_utility.py redirects or kuws redirects
"""
redirects_parser = subparsers.add_parser('redirects', argument_default='-u',
help='Allows you to trace redirects and get other information')
redirects_parser.add_argument('-u', "--url",
help='usage: python main.py redirects -u <url>; Lets you see the trace for a url', nargs='?', dest="trace_url")
As it stands when I run the file nothing actually gets printed to the command line. No help text or errors or anything.
I'm afraid argparse doesn't have any built-in support for this, but you can identify this situation and print the help message:
import sys
if len(sys.argv)==1:
parser.print_help(sys.stderr)
sys.exit(1)
Checking that len(sys.argv)==1 and in that case calling the print_help method of the parser as described in this answer to a similar question is a possible way to print the help message defined in the parser when no arguments are given.
When using subparsers, a common scheme is using set_defaults(func=<function to be called>) and then calling this function (as explained in sub-commands).
You can simply define a first set_defaults(func=help) at first that will be overwritten with the functions of your command.
Note that you can also make the command required when you declare your subparsers (add_subparsers(..., required='True')) and thus, when the user invokes without a command, she will get an error with the usage.

Need to embed `-` character into arguments in python argparse

I am designing a tool to meet some spec. I have a scenario where I want the argument to contain - its string. Pay attention to arg-1 in the below line.
python test.py --arg-1 arg1Data
I am using the argparse library on python27. For some reason the argparse gets confused with the above trial.
My question is how to avoid this? How can I keep the - in my argument?
A sample program (containing the -, if this is removed everything works fine):
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--arg-1", help="increase output verbosity")
args = parser.parse_args()
if args.args-1:
print "verbosity turned on"
Python argparse module replace dashes by underscores, thus:
if args.arg_1:
print "verbosity turned on"
Python doc (second paragraph of section 15.4.3.11. dest) states:
Any internal - characters will be converted to _ characters to make
sure the string is a valid attribute name.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--arg-1", help="increase output verbosity")
parser.add_argument("arg-2")
args = parser.parse_args()
print(args)
produces:
1750:~/mypy$ python stack34970533.py -h
usage: stack34970533.py [-h] [--arg-1 ARG_1] arg-2
positional arguments:
arg-2
optional arguments:
-h, --help show this help message and exit
--arg-1 ARG_1 increase output verbosity
and
1751:~/mypy$ python stack34970533.py --arg-1 xxx yyy
Namespace(arg-2='yyy', arg_1='xxx')
The first argument is an optional. You can use '--arg-1' in commandline, but the value is stored as args.arg_1. Python would interpret args.arg-1 as args.arg - 1. There's a long history of unix commandlines allowing flags with a -. It tries to balance both traditions.
It leaves you in full control of the positionals dest attribute, and does not change the - to _. If you want to access that you have to use the getattr approach. There is bug/issue discussing whether this behavior should be changed or not. But for now, if you want to make it hard on yourself, that's your business.
Internally, argparse accesses the namespace with getattr and setattr to minimize restrictions on the attribute names.

how to set argparse to exclude optional argument by values of positional argument

the arg include an action field and optional switches that modify the behavior of the actions.
the argparse code is like the below:
parser=argparse.ArgumentParser()
parser.add-argument('action',metavar='action', choices=['analysis','report','update'],nargs='?', default='report')
parser.add-argument('-d',dest='delay',type=int, choices=range(1,10),default=1)
parser.add-argument('-v',dest='verbose',action='store-true',default=False)
parser.add-argument('-o',dest='offline',action='store-true',default=False)
parser.add-argument('-n',dest='names',required=False)
i want to make the switch option -o, -d, -v only available for action=report, while option -n only available for action=analysis.
i know there is a mutual group setting, but it is just set for the arguments not for the argument values!
btw, does argparse support combine options, like -vo...???
First: Yes, combined options like -vo are supported
Now to the action switching:
Adding a SubParser to your ArgumentParser is exactely what you want to do.
See Section 15.4.5.1. Sub-commands in the Documentation of argparse, everything is explained to some detail and with example there
EDIT:
I know refer to your comment below this post:
If you do not provide a subcommand parameter in the program call, the parser normally gives you a kind advice how to use the program and then exits with a "too few arguments error"
I rewrote your code to show this
test.py
import argparse
parser=argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='sub-command help')
parser_analysis = subparsers.add_parser('analysis', help='analysis help text')
parser_analysis.add_argument('-n',dest='names',required=False)
parser_report = subparsers.add_parser('report', help='report help text')
parser_report.add_argument('-d',dest='delay',type=int, choices=range(1,10),default=1)
parser_report.add_argument('-v',dest='verbose',action='store_true',default=False)
parser_report.add_argument('-o',dest='offline',action='store_true',default=False)
parser_update = subparsers.add_parser('update', help='update help text')
parser.parse_args()
Now some calls of this test.py with different arguments:
$python test.py
usage: test.py [-h] {analysis,report,update} ...
test.py: error: too few arguments
$python test.py -h
usage: test.py [-h] {analysis,report,update} ...
positional arguments:
{analysis,report,update}
sub-command help
analysis analysis help text
report report help text
update update help text
optional arguments:
-h, --help show this help message and exit
$python test.py report -h
usage: test.py report [-h] [-d {1,2,3,4,5,6,7,8,9}] [-v] [-o]
optional arguments:
-h, --help show this help message and exit
-d {1,2,3,4,5,6,7,8,9}
-v
-o
So as I see it the only problem is that the program throws an error after calling python test.py without any subcommand. So I would do s.th. like this
try:
args=parser.parse_args()
except:
exit(0)
to avoid that the user sees that there was an unhandled error. You have then the same behaviour as i.e. the svn command.
If you want to handle this in the way that a default subcommand is executed, you whould have to do s.th. like mentioned in this post answer:
Argparse - How to Specify a Default Subcommand
import sys
#...your parser definitions
if (len(sys.argv) < 2):
args = parser.parse_args(['update'])
else:
args = parser.parse_args()
This would parse a command update if the argument list in sys.argv is smaller than 2. Why 2? Because the first argument in the argument list is always the program which you called, i.e. test.py
The question is, do you really want this behaviour? Because there is no need for calling test.py update if i can always call test.py, so users will probably get lazy and never use test.py update command. Also if you later want a different default behaviour like test.py starting an interactive mode, users which are by then used to calling test.py for updating will get confused or their scripts which use your program get broken.

gem/git-style command line arguments in Python

Is there a Python module for doing gem/git-style command line arguments? What I mean by gem/git style is:
$ ./MyApp.py
The most commonly used MyApp commands are:
add Add file contents to the index
bisect Find by binary search the change that introduced a bug
branch List, create, or delete branches
checkout Checkout a branch or paths to the working tree
...
$ ./MyApp.py branch
* current-branch
master
With no arguments, the output tells you how you can proceed. And there is a special "help" command:
$ ./MyApp.py help branch
Which gets you deeper tips about the "branch" command.
Edit:
And by doing I mean it does the usage printing for you, exits with invalid input, runs your functions according to your CLI specification. Sort of a "URL mapper" for the command line.
Yes, argparse with add_subparsers().
It's all well explained in the Sub-commands section.
Copying one of the examples from there:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers()
>>> checkout = subparsers.add_parser('checkout', aliases=['co'])
>>> checkout.add_argument('foo')
>>> parser.parse_args(['checkout', 'bar'])
Namespace(foo='bar')
Edit: Unfortunately there's no self generated special help command, but you can get the verbose help message (that you seem to want) with -h or --help like one normally would after the command:
$ ./MyApp.py branch --help
By verbose I don't mean that is like a man page, it's like every other --help kind of help: listing all the arguments, etc...
Example:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(description='Sub description')
>>> checkout = subparsers.add_parser('checkout', description='Checkout description')
>>> checkout.add_argument('foo', help='This is the foo help')
>>> parser.parse_args(['checkout', '--help'])
usage: checkout [-h] foo
Checkout description
positional arguments:
foo This is the foo help
optional arguments:
-h, --help show this help message and exit
If you need to, it should be easy to implement an help command that redirects to --help.
A reasonable hack to get the gem/git style "help" behavior (I just wrote this for what I'm working on anyway):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='sub_commands')
parser_branch = subparsers.add_parser('branch', description='list of branches')
parser_help = subparsers.add_parser('help')
parser_help.add_argument('command', nargs="?", default=None)
# I can't find a legitimate way to set a default subparser in the docs
# If you know of one, please let me know!
if len(sys.argv) < 2:
sys.argv.append('--help')
parsed = parser.parse_args()
if parsed.sub_commands == "help":
if not parsed.command:
parser.parse_args(['--help'])
else:
parser.parse_args([parsed.command, '--help'])
argparse is definitely a step up from optparse and other python solutions I've come across. But IMO the gem/git style of handling args is just a more logical and safer way to do things so it's annoying that it's not supported.
I wanted to do something similar to git commands, where I would load a second script based off of one of the command line options, and have that script populate more command line options, and also have the help work.
I was able to do this by disabling the help option, parse known args, add more arguments, re-enable the help option, and then parse the rest of the arguments.
This is what I came up with.
import argparse
#Note add_help=False
arg_parser = argparse.ArgumentParser(description='Add more arguments after parsing.',add_help=False)
arg_parser.add_argument('MODE', default='default',type=str, help='What commands to use')
args = arg_parser.parse_known_args()[0]
if args.MODE == 'branch':
arg_parser.add_argument('-d', '--delete', default='Delete a branch')
arg_parser.add_argument('-m', '--move', default='move a branch')
elif args.MODE == 'clone' :
arg_parser.add_argument('--local', '-l')
arg_parser.add_argument('--shared')
#Finally re-enable the help option, and reparse the arguments
arg_parser.add_argument(
'-h', '--help',
action='help', default=argparse.SUPPRESS,
help=argparse._('show this help message and exit'))
args = arg_parser.parse_args()

Proper help for arguments

Python optparse works very good when script usage is something like this
%prog [options] [args]
But I need to write help for script with 1 required argument, so usage will be like this
%prog action [options] [args]
You can see something similar when you use Subversion - its usage string is
svn <subcommand> [options] [args]
So my question is: is it possible to prepare help for required argument with optparse in the manner of Subversion? As a result I want to see help like this:
Usage: python myscript.py action [options] [args]
Available actions:
foo
bar
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-v, --verbose Verbose mode. Output debug log to stdout.
I think a good solution for you is argparse, which has been proposed for inclusion in Python 2.7 and 3.2. It handles subcommands, I believe as you want, and the linked page includes a link to a page on porting your code from optparse.
See also the question command-line-arguments-in-python, into which someone edited a list of references that appears to include exactly the same thing you want:
Yes. You can set the usage string like this:
usage = "%prog action [options] [args]"
parser = OptionParser(usage=usage)
parser.add_option("-v", "--verbose",
action="store_true", dest="verbose", default=True,
help="make lots of noise [default]")
Prints the following:
Usage: action [options] [args]
Options:
-h, --help show this help message and exit
-v, --verbose make lots of noise [default]
This was copied almost verbatim from the docs.
Edit:
Based on your comment you could use the description to achieve something similar, though you can't put new-line characters in it.
parser.description = 'Available actions: foo, bar'
Will look like this:
Usage: action [options] [args]
Available actions: foo, bar
Options:
-h, --help show this help message and exit
-v, --verbose make lots of noise [default]
I've run into this problem as well. My solution was to declare commands in a list or tuple, format them into the usage parameter of OptionParser and then use the args list provided by the parser to determine if a command was provided or not, since it technically has to be args[0]. Eg:
self.commands = ('foo', 'bar' ...)
self.parser = <initialized instance of OptionParser>
(self.options, self.args) = parser.parse_args()
if len(self.args) == 0:
self.parser.error("Command required")
self.command = self.args[0]
if not self.command in self.commands:
self.parser.error("Command not recognized")
#... etc
This kinda gets you a command system that looks like Subversion's, but admittedly optparse could be better. I've heard the argparse module is supposed to make it into the stdlib, but with 2.7 being the last of the 2 series releases, I guess you'd have to wait for it to be incorporated into 3.x. Of course you can just install argparse, but that's a drag in some cases.

Categories

Resources