argparse: Subparser with global argument where position does not matter - python

I'll explain the problem with an example. Suppose we have the following code for a random python program:
import argparse
parser = argparse.ArgumentParser(prog="webduino-generator",
description="Webduino source builder")
# Global arguments
parser.add_argument("-v", "--verbose",
action="store_true", dest='verbose',
help="Enable verbose output")
subparsers = parser.add_subparsers(dest="command")
parser_build = subparsers.add_parser("build", help="Build it")
parser_open = subparsers.add_parser("open", help="Open it")
# Check arguments
args = parser.parse_args()
print(args)
Now with this parser, I can do
program.py -v open
which is great! However, I cannot do
program.py open -v
Also the parent/global argument -v will not be listed on the help page of the sub parser.
Is there a way to make this work and add it to the help page of the subparser?

Related

How to set optional arguments to positional arguments in Python's Argparse?

I have the following code:
# Get parsed arguments
args = argparse.ArgumentParser(description=Messages().Get(112))
# Get the arguments for sinit
args.add_argument('init', help=Messages().Get(100), action="store_true")
args.add_argument('--url', default=None, help=Messages().Get(101))
# Get the arguments for schema import
args.add_argument('schema-import', help=Messages().Get(104), action="store_true")
args.add_argument('--file', default=None, help=Messages().Get(104))
The --url argument should only be used with init. For example: script.py schema-import --url should not be accepted but script.py schema-import --file should.
How to set arguments as child arguments?
As mentioned there might be a way to do this with argparse, I'm not sure, but in any event I find it more transparent to explicitly handle argument dependencies in application logic. This should achieve what I think you want:
import argparse
import sys
args = argparse.ArgumentParser(description="please only use the '--url' argument if you also use the 'init' argument")
# Going to use aliases here it's more conventional. So user can use, eg,
# -i or --init for the first argument.
args.add_argument('-i', '--init', help='init help', action="store_true")
args.add_argument('-u', '--url', default=None, help='init help')
args.add_argument('-s', '--schema-import', help='schema-import help', action="store_true")
args.add_argument('-f', '--file', help='file help')
def main():
arguments = args.parse_args()
if arguments.url and not arguments.init:
# You can log an output or raise an exception if you want
# But most likely a print statment is most appropriate
# Since you are interacting with the CLI.
print("You can only use the URL option with init. Exiting")
sys.exit(0)
print("gaurd clauses passed. Here is my code...")
...
if __name__ == "__main__":
main()
Test results (my file called temp.py):
$python temp.py -u https://www.google.com
You can only use the URL option with init. Exiting
$
$python temp.py -i -u https://www.google.com
Gaurd clauses passed. Here is my code...
Why bother with doing all the logic when you can let argparse do all the work for you?
Simply use Sub-commands to define different "branches" of execution:
args = argparse.ArgumentParser(description=Messages().Get(112))
subparsers = args.add_subparsers()
parser_init = subparsers.add_parser('init', help=Messages().Get(100))
parser_init.add_argument('--url', default=None, help=Messages().Get(101))
parser_schema = subparsers.add_parser('schema-import', help=Messages().Get(104))
parser_schema.add_argument('--file', default=None, help=Messages().Get(104))
And this will give you what you want without any logic added:
>>> print(args.parse_args(['schema-import', '--url', "some.url"]))
usage: args.py [-h] {init,schema-import} ...
args.py: error: unrecognized arguments: --url some.url
>>> print(args.parse_args(['schema-import', '--file', "some.file"]))
Namespace(file='some.file')

Displaying help through command line argument

I have a python program that I am running through command line arguments. I have used sys module.
Below is my test.py Python file where I am taking all the args:
if len(sys.argv) > 1:
files = sys.argv
get_input(files)
The get_input method is in another Python file where I have the options defined.
options = {
'--case1': case1,
'--case2': case2,
}
def get_input(arguments):
for file in arguments[1:]:
if file in options:
options[file]()
else:
invalid_input(file)
To run:
python test.py --case1 --case2
My intentions are that I want to show the user all the commands in case they want to read the docs for that.
They should be able to read all the commands like they usually are in all the package for reading help, python test.py --help . With this they should be able to look into all the commands they can run.
How do I do this?
One of the best quality a Python developer can be proud of is to use built-in libraries instead of custom ones. So let's use argparse:
import argparse
# define your command line arguments
parser = argparse.ArgumentParser(description='My application description')
parser.add_argument('--case1', help='It does something', action='store_true')
parser.add_argument('--case2', help='It does something else, I guess', action='store_true')
# parse command line arguments
args = parser.parse_args()
# Accessing arguments values
print('case1 ', args.case1)
print('case2 ', args.case2)
You can now use your cmd arguments like python myscript.py --case1
This comes with a default --help argument you can now use like: python myscript.py --help which will output:
usage: myscript.py [-h] [--case1] [--case2]
My application description
optional arguments:
-h, --help show this help message and exit
--case1 It does something
--case2 It does something else, I guess
Hi you can use option parser and add your options and related help information.
It has by default help option which shows all the available options which you have added.
The detailed document is here. And below is the example.
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename",
help="write report to FILE", metavar="FILE")
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
(options, args) = parser.parse_args()

support version without providing required parameters [duplicate]

I have the following code (using Python 2.7):
# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=False)
parser_shared.add_argument('--version', action='store_true')
# the main parser, inherits from `parser_shared`
parser = argparse.ArgumentParser(description='main', parents=[parser_shared])
# several subcommands, which can't inherit from the main parser, since
# it would expect subcommands ad infinitum
subparsers = parser.add_subparsers('db', parents=[parser_shared])
...
args = parser.parse_args()
Now I would like to be able to call this program e.g. with the --version appended to the normal program or some subcommand:
$ prog --version
0.1
$ prog db --version
0.1
Basically, I need to declare optional subparsers. I'm aware that this isn't really supported, but are there any workarounds or alternatives?
Edit: The error message I am getting:
$ prog db --version
# works fine
$ prog --version
usage: ....
prog: error: too few arguments
According to documentation, --version with action='version' (and not with action='store_true') prints automatically the version number:
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
FWIW, I ran into this also, and ended up "solving" it by not using subparsers (I already had my own system for printing help, so didn't lose anything there).
Instead, I do this:
parser.add_argument("command", nargs="?",
help="name of command to execute")
args, subcommand_args = parser.parse_known_args()
...and then the subcommand creates its own parser (similar to a subparser) which operates only on subcommand_args.
This seems to implement the basic idea of an optional subparser. We parse the standard arguments that apply to all subcommands. Then, if anything is left, we invoke the parser on the rest. The primary arguments are a parent of the subcommand so the -h appears correctly. I plan to enter an interactive prompt if no subcommands are present.
import argparse
p1 = argparse.ArgumentParser( add_help = False )
p1.add_argument( ‘–flag1′ )
p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )
( init_ns, remaining ) = p1.parse_known_args( )
if remaining:
p2.parse_args( args = remaining, namespace=init_ns )
else:
print( ‘Enter interactive loop’ )
print( init_ns )
As discussed in http://bugs.python.org/issue9253 (argparse: optional subparsers), as of Python 3.3, subparsers are now optional. This was an unintended result of a change in how parse_args checked for required arguments.
I found a fudge that restores the previous (required subparsers) behavior, explicitly setting the required attribute of the subparsers action.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True # the fudge
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
See that issue for more details. I expect that if and when this issue gets properly patched, subparsers will be required by default, with some sort of option to set its required attribute to False. But there is a big backlog of argparse patches.
Yeah, I just checked svn, which is used as an object example in the add_subparsers() documentation, and it only supports '--version' on the main command:
python zacharyyoung$ svn log --version
Subcommand 'log' doesn't accept option '--version'
Type 'svn help log' for usage.
Still:
# create common parser
parent_parser = argparse.ArgumentParser('parent', add_help=False)
parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0')
# create the top-level parser
parser = argparse.ArgumentParser(parents=[parent_parser])
subparsers = parser.add_subparsers()
# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo', parents=[parent_parser])
Which yields:
python zacharyyoung$ ./arg-test.py --version
arg-test.py 2.0
python zacharyyoung$ ./arg-test.py foo --version
arg-test.py foo 2.0
While we wait for this feature to be delivered, we can use code like this:
# Make sure that main is the default sub-parser
if '-h' not in sys.argv and '--help' not in sys.argv:
if len(sys.argv) < 2:
sys.argv.append('main')
if sys.argv[1] not in ('main', 'test'):
sys.argv = [sys.argv[0], 'main'] + sys.argv[1:]
Although #eumiro's answer address the --version option, it can only do so because that is a special case for optparse. To allow general invocations of:
prog
prog --verbose
prog --verbose main
prog --verbose db
and have prog --version work the same as prog --verbose main (and prog main --verbose) you can add a method to Argumentparser and call that with the name of the default subparser, just before invoking parse_args():
import argparse
import sys
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_main(args):
print 'main verbose', args.verbose
def do_db(args):
print 'db verbose:', args.verbose
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
subparsers = parser.add_subparsers()
sp = subparsers.add_parser('main')
sp.set_defaults(func=do_main)
sp.add_argument('--verbose', action='store_true')
sp = subparsers.add_parser('db')
sp.set_defaults(func=do_db)
parser.set_default_subparser('main')
args = parser.parse_args()
if hasattr(args, 'func'):
args.func(args)
The set_default_subparser() method is part of the ruamel.std.argparse package.

argparse augment sub-command defaults via global options

I would like to be able to use the global options from an argparse.ArgumentParser object to override/augment the defaults values for a sub-command.
An example would be that displayed help reflects the global updates, i.e., for the following toy example:
import argparse
import os
import sys
# Global parser and options.
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--user",
dest="user",
default=os.environ.get("USER"),
help="Override the setting of the $USER variable.")
# Sub-command parser and options.
subparsers = parser.add_subparsers()
command = subparsers.add_parser(
"command",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
command.add_argument("--config",
dest="config",
default="~%s/config" % os.environ.get("USER"),
help="The config file.")
options = parser.parse_args()
Ideally when when I run this in help mode I would get,
> python example.py --user thing command --help
usage: example.py command [-h] [--config CONFIG]
optional arguments:
-h, --help show this help message and exit
--config CONFIG The config file. (default: ~thing/config)
i.e., the config file path is user specific (thing). I realize that I could change the default config to be "~%(user)s/config" and then resolve this at run-time with the options namespace, however I would like the help to be more explicit.
I gather an alternative solution would be to try to parse the arguments once to obtain the global options, i.e.,
if "--help" in sys.argv:
# Parse the command minus the help to obtain the global options.
args = sys.argv[1:]
args.remove("--help")
# Update the defaults with the global options.
options = parser.parse_args(args)
command.set_defaults(config="~%s/config" % options.user)
# Re-parse the options.
parser.parse_args()
Though this seems somewhat hacky. Is there a better approach/solution?
After defining the global options, but before defining the subcommands, call parse_known_args to find out what the value of --user is. Then, finish defining the subparser commands, using the value of --user to define the default of --config, before
calling parse_args to parse all options on the command line.
This is a little different from your alternative, but it keeps all the command-line processing inside the argparse object.
(I trimmed down your code a little just to shorten it for this answer.)
import os
import argparse
# Global preparser and options.
preparser = argparse.ArgumentParser(add_help=False)
preparser.add_argument("--user", dest="user", default=os.environ.get("USER"))
# ****** NEW *******
options, _ = preparser.parse_known_args() # Ignore what we haven't defined yet
user = options.user # Use this to define the default of --config
parser = argparse.ArgumentParser(parents=[ preparser ], add_help=True,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
# Sub-command parser and options.
subparsers = parser.add_subparsers()
command = subparsers.add_parser("command")
# ****** MODIFIED *******
command.add_argument("--config", dest="config", default="~%s/config" % (user,))
options = parser.parse_args()
Here's a solution that modifies the help line directly, at runtime. It could also modify os.environ or some other global as well, but I'll keep it simple. The key is assigning the action created by add_argument('--config'...) to a variable. help is just an attribute of that variable. You are free to modify that.
class FooAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
config.help = 'new user (%s)'% values
setattr(namespace, self.dest, values)
parser = argparse.ArgumentParser()
parser.add_argument('--user',action=FooAction)
sub = parser.add_subparsers()
cmd = sub.add_parser('cmd')
config = cmd.add_argument('--config',help='initial help')
config.help = 'default help' # alt way of setting 'help'
# print config # to see other attributes of the config action
args = parser.parse_args()
print args
Invoked with just cmd -h we get the default help
$ python stack12167228.py cmd -h
usage: stack12167228.py cmd [-h] [--config CONFIG]
optional arguments:
-h, --help show this help message and exit
--config CONFIG default help
Invoked with --user xxx cmd -h, the help is customized
$ python stack12167228.py --user xxx cmd -h
usage: stack12167228.py cmd [-h] [--config CONFIG]
optional arguments:
-h, --help show this help message and exit
--config CONFIG new user (xxx)

Display help message with Python argparse when script is called without any arguments

Assume I have a program that uses argparse to process command line arguments/options. The following will print the 'help' message:
./myprogram -h
or:
./myprogram --help
But, if I run the script without any arguments whatsoever, it doesn't do anything. What I want it to do is to display the usage message when it is called with no arguments. How is that done?
This answer comes from Steven Bethard on Google groups. I'm reposting it here to make it easier for people without a Google account to access.
You can override the default behavior of the error method:
import argparse
import sys
class MyParser(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(2)
parser = MyParser()
parser.add_argument('foo', nargs='+')
args = parser.parse_args()
Note that the above solution will print the help message whenever the error
method is triggered. For example, test.py --blah will print the help message
too if --blah isn't a valid option.
If you want to print the help message only if no arguments are supplied on the
command line, then perhaps this is still the easiest way:
import argparse
import sys
parser=argparse.ArgumentParser()
parser.add_argument('foo', nargs='+')
if len(sys.argv)==1:
parser.print_help(sys.stderr)
sys.exit(1)
args=parser.parse_args()
Note that parser.print_help() prints to stdout by default. As init_js suggests, use parser.print_help(sys.stderr) to print to stderr.
Instead of writing a class, a try/except can be used instead
try:
options = parser.parse_args()
except:
parser.print_help()
sys.exit(0)
The upside is that the workflow is clearer and you don't need a stub class. The downside is that the first 'usage' line is printed twice.
This will need at least one mandatory argument. With no mandatory arguments, providing zero args on the commandline is valid.
With argparse you could use ArgumentParser.print_usage():
parser.argparse.ArgumentParser()
# parser.add_args here
# sys.argv includes a list of elements starting with the program
if len(sys.argv) < 2:
parser.print_usage()
sys.exit(1)
Printing help
ArgumentParser.print_usage(file=None)
  Print a brief description of how the ArgumentParser should be invoked on the command line. If file is None, sys.stdout is assumed.
The cleanest solution will be to manually pass default argument if none were given on the command line:
parser.parse_args(args=None if sys.argv[1:] else ['--help'])
Complete example:
import argparse, sys
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost', help='Host to connect to')
# parse arguments
args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])
# use your args
print("connecting to {}".format(args.host))
This will print complete help (not short usage) if called w/o arguments.
If you associate default functions for (sub)parsers, as is mentioned under add_subparsers, you can simply add it as the default action:
parser = argparse.ArgumentParser()
parser.set_defaults(func=lambda x: parser.print_usage())
args = parser.parse_args()
args.func(args)
Add the try-except if you raise exceptions due to missing positional arguments.
If you have arguments that must be specified for the script to run - use the required parameter for ArgumentParser as shown below:-
parser.add_argument('--foo', required=True)
parse_args() will report an error if the script is run without any arguments.
Throwing my version into the pile here:
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()
if not vars(args):
parser.print_help()
parser.exit(1)
You may notice the parser.exit - I mainly do it like that because it saves an import line if that was the only reason for sys in the file...
There are a pair of one-liners with sys.argv[1:] (a very common Python's idiom to refer the command line arguments, being sys.argv[0] the script's name) that can do the job.
The first one is self-explanatory, clean and pythonic:
args = parser.parse_args(None if sys.argv[1:] else ['-h'])
The second one is a little hackier. Combining the previously evaluated fact that an empty list is False with the True == 1 and False == 0 equivalences you get this:
args = parser.parse_args([None, ['-h']][not sys.argv[1:]])
Maybe too many brackets, but pretty clear if a previous argument selection was made.
_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])
parser.print_help()
parser.exit()
The parser.exit method also accept a status (returncode), and a message value (include a trailing newline yourself!).
an opinionated example,
:)
#!/usr/bin/env python3
""" Example argparser based python file
"""
import argparse
ARGP = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawTextHelpFormatter,
)
ARGP.add_argument('--example', action='store_true', help='Example Argument')
def main(argp=None):
if argp is None:
argp = ARGP.parse_args() # pragma: no cover
if 'soemthing_went_wrong' and not argp.example:
ARGP.print_help()
ARGP.exit(status=64, message="\nSomething went wrong, --example condition was not set\n")
if __name__ == '__main__':
main() # pragma: no cover
Example calls:
$ python3 ~/helloworld.py; echo $?
usage: helloworld.py [-h] [--example]
Example argparser based python file
optional arguments:
-h, --help show this help message and exit
--example Example Argument
Something went wrong, --example condition was not set
64
$ python3 ~/helloworld.py --example; echo $?
0
Most of the answers here required another module, such as sys, to be imported or were using optional arguments. I wanted to discover an answer that used only argparse, worked with required arguments, and if possible worked without catching exceptions. I ended up with the following:
import argparse
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser(add_help=False)
arg_parser.add_argument('input_file', type=str, help='The path to the input file.')
arg_parser.add_argument('output_file', type=str, help='The path to the output file.')
arg_parser.add_argument('-h','--help', action='store_true', help='show this help message and exit')
arg_parser.usage = arg_parser.format_help()
args = arg_parser.parse_args()
The main idea was to use the format_help function in order to provide the help string to the usage statement. Setting add_help to False in the call to ArgumentParser() prevents the help statement from printing twice in certain circumstances. However, I had to create an argument for the optional help argument that mimicked the typical help message once it was set to False in order to display the optional help argument in the help message. The action is set to store_true in the help argument to prevent the help message from filling in a value like HELP for the parameter when it prints the help message.
So for a really simple answer. Most of the time with argparse you are checking to see if parameters are set anyway, to call a function that does something.
If no parameters, just else out at the end and print the help. Simple and works.
import argparse
import sys
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("--holidays", action='store_true')
group.add_argument("--people", action='store_true')
args=parser.parse_args()
if args.holidays:
get_holidays()
elif args.people:
get_people()
else:
parser.print_help(sys.stderr)
Here is another way to do it, if you need something flexible where you want to display help if specific params are passed, none at all or more than 1 conflicting arg:
import argparse
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--days', required=False, help="Check mapped inventory that is x days old", default=None)
parser.add_argument('-e', '--event', required=False, action="store", dest="event_id",
help="Check mapped inventory for a specific event", default=None)
parser.add_argument('-b', '--broker', required=False, action="store", dest="broker_id",
help="Check mapped inventory for a broker", default=None)
parser.add_argument('-k', '--keyword', required=False, action="store", dest="event_keyword",
help="Check mapped inventory for a specific event keyword", default=None)
parser.add_argument('-p', '--product', required=False, action="store", dest="product_id",
help="Check mapped inventory for a specific product", default=None)
parser.add_argument('-m', '--metadata', required=False, action="store", dest="metadata",
help="Check mapped inventory for specific metadata, good for debugging past tix", default=None)
parser.add_argument('-u', '--update', required=False, action="store_true", dest="make_updates",
help="Update the event for a product if there is a difference, default No", default=False)
args = parser.parse_args()
days = args.days
event_id = args.event_id
broker_id = args.broker_id
event_keyword = args.event_keyword
product_id = args.product_id
metadata = args.metadata
make_updates = args.make_updates
no_change_counter = 0
change_counter = 0
req_arg = bool(days) + bool(event_id) + bool(broker_id) + bool(product_id) + bool(event_keyword) + bool(metadata)
if not req_arg:
print("Need to specify days, broker id, event id, event keyword or past tickets full metadata")
parser.print_help()
sys.exit()
elif req_arg != 1:
print("More than one option specified. Need to specify only one required option")
parser.print_help()
sys.exit()
# Processing logic here ...
Cheers!
I like to keep things as simple as possible, this works great:
#!/usr/bin/env python3
Description = """Tool description"""
Epilog = """toolname.py -a aflag -b bflag with these combined it does blah"""
arg_parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=Description,
epilog=Epilog,
)
try:
if len(sys.argv) == 1:
arg_parser.print_help()
except Exception as e:
print(e)
This is how I start all my tools as its always good to include some examples
When call add_subparsers method save the first positional argument to dest= and check value after argparse has been initialized, like this:
subparsers = parser.add_subparsers(dest='command')
And just check this this variable:
if not args.command:
parser.print_help()
parser.exit(1) # If exit() - exit code will be zero (no error)
Full example:
#!/usr/bin/env python
""" doc """
import argparse
import sys
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest='command',
help='List of commands')
list_parser = subparsers.add_parser('list',
help='List contents')
list_parser.add_argument('dir', action='store',
help='Directory to list')
create_parser = subparsers.add_parser('create',
help='Create a directory')
create_parser.add_argument('dirname', action='store',
help='New directory to create')
create_parser.add_argument('--read-only', default=False, action='store_true',
help='Set permissions to prevent writing to the directory')
args = parser.parse_args()
if not args.command:
parser.print_help()
parser.exit(1)
print(vars(args)) # For debug
This approach is a lot more elegant than most others. Instead of overriding error(), you can control the behaviour a lot more precisely by wrapping the parse_args() method:
import sys
import argparse
HelpFlags = ('help', '--help', '-h', '/h', '?', '/?', )
class ArgParser (argparse.ArgumentParser):
def __init__(self, *args, **kws):
super().__init__(*args, **kws)
def parse_args(self, args=None, namespace=None):
if args is None:
args = sys.argv[1:]
if len(args) < 1 or (args[0].lower() in HelpFlags):
self.print_help(sys.stderr)
sys.exit()
return super().parse_args(args, namespace)
Set your positional arguments with nargs, and check if positional args are empty.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?')
args = parser.parse_args()
if not args.file:
parser.print_help()
Reference Python nargs
If your command is something where a user needs to choose some action, then use a mutually exclusive group with required=True.
This is kind of an extension to the answer given by pd321.
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--batch", action='store', type=int, metavar='pay_id')
group.add_argument("--list", action='store_true')
group.add_argument("--all", action='store_true', help='check all payments')
args=parser.parse_args()
if args.batch:
print('batch {}'.format(args.batch))
if args.list:
print('list')
if args.all:
print('all')
Output:
$ python3 a_test.py
usage: a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: error: one of the arguments --batch --list --all is required
This only give the basic help. And some of the other answers will give you the full help. But at least your users know they can do -h
This isn't good (also, because intercepts all errors), but:
def _error(parser):
def wrapper(interceptor):
parser.print_help()
sys.exit(-1)
return wrapper
def _args_get(args=sys.argv[1:]):
parser = argparser.ArgumentParser()
parser.error = _error(parser)
parser.add_argument(...)
...
Here is definition of the error function of the ArgumentParser class.
As you see, the following signature takes two arguments. However, functions outside the class know nothing about first argument self, because, roughly speaking, this argument is for the class.
def _error(self, message):
self.print_help()
sys.exit(-1)
def _args_get(args=sys.argv[1:]):
parser = argparser.ArgumentParser()
parser.error = _error
...
will output:
"AttributeError: 'str' object has no attribute 'print_help'"
You can pass parser (self) in _error function, by calling it:
def _error(self, message):
self.print_help()
sys.exit(-1)
def _args_get(args=sys.argv[1:]):
parser = argparser.ArgumentParser()
parser.error = _error(parser)
...
But if you don't want exit the program right now, return it:
def _error(parser):
def wrapper():
parser.print_help()
sys.exit(-1)
return wrapper
Nonetheless, parser doesn't know that it has been modified. Thus, when an error occurs, it will print the cause of it (by the way, it's a localized translation). So intercept it:
def _error(parser):
def wrapper(interceptor):
parser.print_help()
sys.exit(-1)
return wrapper
Now, when an error occurs, parser will print the cause of it, and you'll intercept it, look at it, and... throw out.

Categories