Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default sub-command, or handling no sub-command with argparse

How can I have a default sub-command, or handle the case where no sub-command is given using argparse?

import argparse  a = argparse.ArgumentParser() b = a.add_subparsers() b.add_parser('hi') a.parse_args() 

Here I'd like a command to be selected, or the arguments to be handled based only on the next highest level of parser (in this case the top-level parser).

joiner@X:~/src> python3 default_subcommand.py usage: default_subcommand.py [-h] {hi} ... default_subcommand.py: error: too few arguments
like image 713
Matt Joiner Avatar asked Jun 15 '11 23:06

Matt Joiner


People also ask

How do you add an optional argument in Argparse?

To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.

What is Argparse ArgumentParser ()?

The argparse module provides a convenient interface to handle command-line arguments. It displays the generic usage of the program, help, and errors. The parse_args() function of the ArgumentParser class parses arguments and adds value as an attribute dest of the object.

What is Store_true in Python?

The store_true option automatically creates a default value of False. Likewise, store_false will default to True when the command-line argument is not present.


2 Answers

It seems I've stumbled on the solution eventually myself.

If the command is optional, then this makes the command an option. In my original parser configuration, I had a package command that could take a range of possible steps, or it would perform all steps if none was given. This makes the step a choice:

parser = argparse.ArgumentParser()  command_parser = subparsers.add_parser('command') command_parser.add_argument('--step', choices=['prepare', 'configure', 'compile', 'stage', 'package'])  ...other command parsers  parsed_args = parser.parse_args()  if parsed_args.step is None:     do all the steps... 
like image 40
Matt Joiner Avatar answered Oct 13 '22 22:10

Matt Joiner


On Python 3.2 (and 2.7) you will get that error, but not on 3.3 and 3.4 (no response). Therefore on 3.3/3.4 you could test for parsed_args to be an empty Namespace.

A more general solution is to add a method set_default_subparser() (taken from the ruamel.std.argparse package) and call that method just before parse_args():

import argparse import sys  def set_default_subparser(self, name, args=None, positional_args=0):     """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 last position before global positional             # arguments, this implies no global options are specified after             # first positional argument             if args is None:                 sys.argv.insert(len(sys.argv) - positional_args, name)             else:                 args.insert(len(args) - positional_args, name)  argparse.ArgumentParser.set_default_subparser = set_default_subparser  def do_hi():     print('inside hi')  a = argparse.ArgumentParser() b = a.add_subparsers() sp = b.add_parser('hi') sp.set_defaults(func=do_hi)  a.set_default_subparser('hi') parsed_args = a.parse_args()  if hasattr(parsed_args, 'func'):     parsed_args.func() 

This will work with 2.6 (if argparse is installed from PyPI), 2.7, 3.2, 3.3, 3.4. And allows you to do both

python3 default_subcommand.py 

and

python3 default_subcommand.py hi 

with the same effect.

Allowing to chose a new subparser for default, instead of one of the existing ones.

The first version of the code allows setting one of the previously-defined subparsers as a default one. The following modification allows adding a new default subparser, which could then be used to specifically process the case when no subparser was selected by user (different lines marked in the code)

def set_default_subparser(self, name, args=None, positional_args=0):     """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     existing_default = False # check if default parser previously defined     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 sp_name == name: # check existance of default parser                     existing_default = True         if not subparser_found:             # If the default subparser is not among the existing ones,             # create a new parser.             # As this is called just before 'parse_args', the default             # parser created here will not pollute the help output.              if not existing_default:                 for x in self._subparsers._actions:                     if not isinstance(x, argparse._SubParsersAction):                         continue                     x.add_parser(name)                     break # this works OK, but should I check further?              # insert default in last position before global positional             # arguments, this implies no global options are specified after             # first positional argument             if args is None:                 sys.argv.insert(len(sys.argv) - positional_args, name)             else:                 args.insert(len(args) - positional_args, name)  argparse.ArgumentParser.set_default_subparser = set_default_subparser  a = argparse.ArgumentParser() b = a.add_subparsers(dest ='cmd') sp = b.add_parser('hi') sp2 = b.add_parser('hai')  a.set_default_subparser('hey') parsed_args = a.parse_args()  print(parsed_args) 

The "default" option will still not show up in the help:

python test_parser.py -h usage: test_parser.py [-h] {hi,hai} ...  positional arguments:   {hi,hai}  optional arguments:   -h, --help  show this help message and exit 

However, it is now possible to differentiate between and separately handle calling one of the provided subparsers, and calling the default subparser when no argument was provided:

$ python test_parser.py hi Namespace(cmd='hi') $ python test_parser.py  Namespace(cmd='hey') 
like image 83
Anthon Avatar answered Oct 13 '22 21:10

Anthon