Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 873
John Avatar asked Oct 07 '22 19:10

John


1 Answers

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()
like image 145
chepner Avatar answered Oct 10 '22 03:10

chepner