How to parse multiple nested sub-commands using python argparse?
Asked Answered
S

14

114

I am implementing a command line program which has interface like this:

cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...]

I have gone through the argparse documentation. I can implement GLOBAL_OPTIONS as optional argument using add_argument in argparse. And the {command [COMMAND_OPTS]} using Sub-commands.

From the documentation it seems I can have only one sub-command. But as you can see I have to implement one or more sub-commands. What is the best way to parse such command line arguments useing argparse?

Selemas answered 4/5, 2012 at 11:44 Comment(2)
I don't think this is what sub-commands is intended for. From the documentation it states that this is, in essence, for controlling separate distinct subprograms. Have you looked into argument groups?Valance
distutils ./setup.py also has this style CLI interface, would be interesting to look into their source code.Foregather
S
28

@mgilson has a nice answer to this question. But problem with splitting sys.argv myself is that I lose the nice help message Argparse generates for the user. So I ended up doing this:

import argparse

## This function takes the 'extra' attribute from global namespace and
## re-parses it to create separate namespaces for all other chained commands.
def parse_extra (parser, namespace):
  namespaces = []
  extra = namespace.extra
  while extra:
    n = parser.parse_args(extra)
    extra = n.extra
    namespaces.append(n)

  return namespaces

argparser=argparse.ArgumentParser()
subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')

parser_a = subparsers.add_parser('command_a', help = "command_a help")
## Setup options for parser_a

## Add nargs="*" for zero or more other commands
argparser.add_argument('extra', nargs = "*", help = 'Other commands')

## Do similar stuff for other sub-parsers

Now after first parse all chained commands are stored in extra. I reparse it while it is not empty to get all the chained commands and create separate namespaces for them. And i get nicer usage string that argparse generates.

Selemas answered 14/5, 2012 at 8:36 Comment(6)
@Flavius, after I get namespace from the parser by calling namespace = argparser.parse_args(), I call parse_extra with parser and namespace. extra_namespaces = parse_extra( argparser, namespace )Selemas
I think I understand the logic, but what is parser in the code that you have. I only see it being used to add the extra argument. Then you mentioned it again in the above comment. Is it supposed to be argparser?Tessin
@Tessin yeah it should be argparser. Will edit it.Selemas
Note that this solution fails for subcommand-specific optional arguments. See my solution below (https://mcmap.net/q/193245/-how-to-parse-multiple-nested-sub-commands-using-python-argparse) for an alternative solution.Ludwick
Here is an example of how this fails. Add the following 3 lines: parser_b = subparsers.add_parser('command_b', help='command_b help'); parser_b.add_argument('--baz', choices='XYZ', help='baz help'); options = argparser.parse_args(['--foo', 'command_a', 'command_b', '--baz', 'Z']); This fails with an error PROG: error: unrecognized arguments: --baz Z. The reason is that during the parsing of command_a, the optional arguments of command_b are already parsed (and are unknown for the subparser of command_a).Ludwick
I was able to work with import argparse parser = argparse.ArgumentParser() parser.add_argument('--bar', type=int, help='bar help') parser.add_argument('commands', nargs='*', metavar='COMMAND', choices=['command_a', 'command_b'])Polycotyledon
D
41

I came up with the same qustion, and it seems i have got a better answer.

The solution is we shall not simply nest subparser with another subparser, but we can add subparser following with a parser following another subparser.

Here is code to show this:

parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--user', '-u',
                    default=getpass.getuser(),
                    help='username')
parent_parser.add_argument('--debug', default=False, required=False,
                        action='store_true', dest="debug", help='debug flag')
main_parser = argparse.ArgumentParser()
service_subparsers = main_parser.add_subparsers(title="service",
                    dest="service_command")
service_parser = service_subparsers.add_parser("first", help="first",
                    parents=[parent_parser])
action_subparser = service_parser.add_subparsers(title="action",
                    dest="action_command")
action_parser = action_subparser.add_parser("second", help="second",
                    parents=[parent_parser])

args = main_parser.parse_args()
Downspout answered 20/10, 2013 at 10:15 Comment(4)
Yes, argparse does allow nested subparsers. But I've only seen them used in one other place - in a test case for a Python issue, bugs.python.org/issue14365Unfriendly
This assumes that the commands have a nested structure. But the question is asking for "parallel" commandsFlatto
I am trying to create multiple subparsers like service in this case since I demarcation of commands. It errs out saying I cannot have duplicate commands? I want some thing like this command --user us pass --debug false subcommandone subcommandoption or command --user us pass --debug false subcommandtwo subcommandoption or commandtwo --user us pass subcommandthree subcommandoption. I intend to add complete help docs accordingly also. The code will be run as python test.py command --user us pass --debug false subcommandtwo subcommandoptionPolycotyledon
Nice add-on I got from https://mcmap.net/q/41373/-is-there-a-built-in-function-to-print-all-the-current-properties-and-values-of-an-object: Add a final pprint(vars(args)) to see what argparse offers as arguments. Very helpful while debugging the parsers.Inosculate
S
28

@mgilson has a nice answer to this question. But problem with splitting sys.argv myself is that I lose the nice help message Argparse generates for the user. So I ended up doing this:

import argparse

## This function takes the 'extra' attribute from global namespace and
## re-parses it to create separate namespaces for all other chained commands.
def parse_extra (parser, namespace):
  namespaces = []
  extra = namespace.extra
  while extra:
    n = parser.parse_args(extra)
    extra = n.extra
    namespaces.append(n)

  return namespaces

argparser=argparse.ArgumentParser()
subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')

parser_a = subparsers.add_parser('command_a', help = "command_a help")
## Setup options for parser_a

## Add nargs="*" for zero or more other commands
argparser.add_argument('extra', nargs = "*", help = 'Other commands')

## Do similar stuff for other sub-parsers

Now after first parse all chained commands are stored in extra. I reparse it while it is not empty to get all the chained commands and create separate namespaces for them. And i get nicer usage string that argparse generates.

Selemas answered 14/5, 2012 at 8:36 Comment(6)
@Flavius, after I get namespace from the parser by calling namespace = argparser.parse_args(), I call parse_extra with parser and namespace. extra_namespaces = parse_extra( argparser, namespace )Selemas
I think I understand the logic, but what is parser in the code that you have. I only see it being used to add the extra argument. Then you mentioned it again in the above comment. Is it supposed to be argparser?Tessin
@Tessin yeah it should be argparser. Will edit it.Selemas
Note that this solution fails for subcommand-specific optional arguments. See my solution below (https://mcmap.net/q/193245/-how-to-parse-multiple-nested-sub-commands-using-python-argparse) for an alternative solution.Ludwick
Here is an example of how this fails. Add the following 3 lines: parser_b = subparsers.add_parser('command_b', help='command_b help'); parser_b.add_argument('--baz', choices='XYZ', help='baz help'); options = argparser.parse_args(['--foo', 'command_a', 'command_b', '--baz', 'Z']); This fails with an error PROG: error: unrecognized arguments: --baz Z. The reason is that during the parsing of command_a, the optional arguments of command_b are already parsed (and are unknown for the subparser of command_a).Ludwick
I was able to work with import argparse parser = argparse.ArgumentParser() parser.add_argument('--bar', type=int, help='bar help') parser.add_argument('commands', nargs='*', metavar='COMMAND', choices=['command_a', 'command_b'])Polycotyledon
U
18

parse_known_args returns a Namespace and a list of unknown strings. This is similar to the extra in the checked answer.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
sub = parser.add_subparsers()
for i in range(1,4):
    sp = sub.add_parser('cmd%i'%i)
    sp.add_argument('--foo%i'%i) # optionals have to be distinct

rest = '--foo 0 cmd2 --foo2 2 cmd3 --foo3 3 cmd1 --foo1 1'.split() # or sys.argv
args = argparse.Namespace()
while rest:
    args,rest =  parser.parse_known_args(rest,namespace=args)
    print args, rest

produces:

Namespace(foo='0', foo2='2') ['cmd3', '--foo3', '3', 'cmd1', '--foo1', '1']
Namespace(foo='0', foo2='2', foo3='3') ['cmd1', '--foo1', '1']
Namespace(foo='0', foo1='1', foo2='2', foo3='3') []

An alternative loop would give each subparser its own namespace. This allows overlap in positionals names.

argslist = []
while rest:
    args,rest =  parser.parse_known_args(rest)
    argslist.append(args)
Unfriendly answered 20/10, 2013 at 18:19 Comment(2)
Works nicely. There is a flaw however: if there is a misspelled option somewhere (e.g. rest = '--foo 0 cmd2 --foo2 2 --bar cmd3 --foo3 3 cmd1 --foo1 1'.split()), then argparse will end in error: too few arguments instead of pointing out the invalid option. This is because the bad option will be left in rest until we are out of command arguments.Pomelo
The comment # or sys.argv should be # or sys.argv[1:].Pomelo
L
17

The solution provide by @Vikas fails for subcommand-specific optional arguments, but the approach is valid. Here is an improved version:

import argparse

# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')

# create the parser for the "command_a" command
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')

# create the parser for the "command_b" command
parser_b = subparsers.add_parser('command_b', help='command_b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')

# parse some argument lists
argv = ['--foo', 'command_a', '12', 'command_b', '--baz', 'Z']
while argv:
    print(argv)
    options, argv = parser.parse_known_args(argv)
    print(options)
    if not options.subparser_name:
        break

This uses parse_known_args instead of parse_args. parse_args aborts as soon as a argument unknown to the current subparser is encountered, parse_known_args returns them as a second value in the returned tuple. In this approach, the remaining arguments are fed again to the parser. So for each command, a new Namespace is created.

Note that in this basic example, all global options are added to the first options Namespace only, not to the subsequent Namespaces.

This approach works fine for most situations, but has three important limitations:

  • It is not possible to use the same optional argument for different subcommands, like myprog.py command_a --foo=bar command_b --foo=bar.
  • It is not possible to use any variable length positional arguments with subcommands (nargs='?' or nargs='+' or nargs='*').
  • Any known argument is parsed, without 'breaking' at the new command. E.g. in PROG --foo command_b command_a --baz Z 12 with the above code, --baz Z will be consumed by command_b, not by command_a.

These limitations are a direct limitation of argparse. Here is a simple example that shows the limitations of argparse -even when using a single subcommand-:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('spam', nargs='?')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')

# create the parser for the "command_a" command
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')

# create the parser for the "command_b" command
parser_b = subparsers.add_parser('command_b', help='command_b help')

options = parser.parse_args('command_a 42'.split())
print(options)

This will raise the error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b').

The cause is that the internal method argparse.ArgParser._parse_known_args() it is too greedy and assumes that command_a is the value of the optional spam argument. In particular, when 'splitting' up optional and positional arguments, _parse_known_args() does not look at the names of the arugments (like command_a or command_b), but merely where they occur in the argument list. It also assumes that any subcommand will consume all remaining arguments. This limitation of argparse also prevents a proper implementation of multi-command subparsers. This unfortunately means that a proper implementation requires a full rewrite of the argparse.ArgParser._parse_known_args() method, which is 200+ lines of code.

Given these limitation, it may be an options to simply revert to a single multiple-choice argument instead of subcommands:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--bar', type=int, help='bar help')
parser.add_argument('commands', nargs='*', metavar='COMMAND',
                 choices=['command_a', 'command_b'])

options = parser.parse_args('--bar 2 command_a command_b'.split())
print(options)
#options = parser.parse_args(['--help'])

It is even possible to list the different commands in the usage information, see my answer https://mcmap.net/q/195631/-python-argparse-list-individual-choices-in-the-usage

Ludwick answered 23/4, 2018 at 9:33 Comment(0)
F
6

Improving on the answer by @mgilson, I wrote a small parsing method which splits argv into parts and puts values of arguments of commands into hierarchy of namespaces:

import sys
import argparse


def parse_args(parser, commands):
    # Divide argv by commands
    split_argv = [[]]
    for c in sys.argv[1:]:
        if c in commands.choices:
            split_argv.append([c])
        else:
            split_argv[-1].append(c)
    # Initialize namespace
    args = argparse.Namespace()
    for c in commands.choices:
        setattr(args, c, None)
    # Parse each command
    parser.parse_args(split_argv[0], namespace=args)  # Without command
    for argv in split_argv[1:]:  # Commands
        n = argparse.Namespace()
        setattr(args, argv[0], n)
        parser.parse_args(argv, namespace=n)
    return args


parser = argparse.ArgumentParser()
commands = parser.add_subparsers(title='sub-commands')

cmd1_parser = commands.add_parser('cmd1')
cmd1_parser.add_argument('--foo')

cmd2_parser = commands.add_parser('cmd2')
cmd2_parser.add_argument('--foo')

cmd2_parser = commands.add_parser('cmd3')
cmd2_parser.add_argument('--foo')


args = parse_args(parser, commands)
print(args)

It behaves properly, providing nice argparse help:

For ./test.py --help:

usage: test.py [-h] {cmd1,cmd2,cmd3} ...

optional arguments:
  -h, --help        show this help message and exit

sub-commands:
  {cmd1,cmd2,cmd3}

For ./test.py cmd1 --help:

usage: test.py cmd1 [-h] [--foo FOO]

optional arguments:
  -h, --help  show this help message and exit
  --foo FOO

And creates a hierarchy of namespaces containing the argument values:

./test.py cmd1 --foo 3 cmd3 --foo 4
Namespace(cmd1=Namespace(foo='3'), cmd2=None, cmd3=Namespace(foo='4'))
Fayum answered 11/5, 2017 at 23:57 Comment(3)
Reviewing your code above, I ran into one problem. On line 18, you refer to split_argv[0] which is actually empty in split_argv, because you append [c] to split_argv (intially set to [[]]). If you change line 7 to split_argv = [], everything works as expected.Protuberance
I did some more fixes (again) to the code you shared (fixing some issues I was running into) and ended up with this: gist.github.com/anonymous/f4be805fc3ff9e132eb1e1aa0b4f7d4bProtuberance
This answer is pretty decent, you can determine which subparser was used by adding dest to the add_subparsers method #8250510Leesa
S
5

You can always split up the command-line yourself (split sys.argv on your command names), and then only pass the portion corresponding to the particular command to parse_args -- You can even use the same Namespace using the namespace keyword if you want.

Grouping the commandline is easy with itertools.groupby:

import sys
import itertools
import argparse    

mycommands=['cmd1','cmd2','cmd3']

def groupargs(arg,currentarg=[None]):
    if(arg in mycommands):currentarg[0]=arg
    return currentarg[0]

commandlines=[list(args) for cmd,args in intertools.groupby(sys.argv,groupargs)]

#setup parser here...
parser=argparse.ArgumentParser()
#...

namespace=argparse.Namespace()
for cmdline in commandlines:
    parser.parse_args(cmdline,namespace=namespace)

#Now do something with namespace...

untested

Scranton answered 4/5, 2012 at 13:0 Comment(2)
Thanks mgilson. This is a nice solution to my question, but i ended up doing it little differently. I added another answer.Selemas
Nice use of itertools.groupby()! This is how I did the same thing before I knew about groupby().Canicula
S
5

You could try arghandler. This is an extension to argparse with explicit support for subcommands.

Snuffbox answered 28/7, 2015 at 18:29 Comment(1)
arghandler provides a nice way for declaring subcommands. However I don't see how this helps to solver OP's question: parsing multiple subcommands. The first subcommand parsed will eat up all remaining arguments, so further commands will never be parsed. Please give a hint on how to solve this with arghandler. Thanks.Pomelo
G
4

Built a full Python 2/3 example with subparsers, parse_known_args and parse_args (running on IDEone):

from __future__ import print_function

from argparse import ArgumentParser
from random import randint


def main():
    parser = get_parser()

    input_sum_cmd = ['sum_cmd', '--sum']
    input_min_cmd = ['min_cmd', '--min']

    args, rest = parser.parse_known_args(
        # `sum`
        input_sum_cmd +
        ['-a', str(randint(21, 30)),
         '-b', str(randint(51, 80))] +
        # `min`
        input_min_cmd +
        ['-y', str(float(randint(64, 79))),
         '-z', str(float(randint(91, 120)) + .5)]
    )

    print('args:\t ', args,
          '\nrest:\t ', rest, '\n', sep='')

    sum_cmd_result = args.sm((args.a, args.b))
    print(
        'a:\t\t {:02d}\n'.format(args.a),
        'b:\t\t {:02d}\n'.format(args.b),
        'sum_cmd: {:02d}\n'.format(sum_cmd_result), sep='')

    assert rest[0] == 'min_cmd'
    args = parser.parse_args(rest)
    min_cmd_result = args.mn((args.y, args.z))
    print(
        'y:\t\t {:05.2f}\n'.format(args.y),
        'z:\t\t {:05.2f}\n'.format(args.z),
        'min_cmd: {:05.2f}'.format(min_cmd_result), sep='')

def get_parser():
    # create the top-level parser
    parser = ArgumentParser(prog='PROG')
    subparsers = parser.add_subparsers(help='sub-command help')

    # create the parser for the "sum" command
    parser_a = subparsers.add_parser('sum_cmd', help='sum some integers')
    parser_a.add_argument('-a', type=int,
                          help='an integer for the accumulator')
    parser_a.add_argument('-b', type=int,
                          help='an integer for the accumulator')
    parser_a.add_argument('--sum', dest='sm', action='store_const',
                          const=sum, default=max,
                          help='sum the integers (default: find the max)')

    # create the parser for the "min" command
    parser_b = subparsers.add_parser('min_cmd', help='min some integers')
    parser_b.add_argument('-y', type=float,
                          help='an float for the accumulator')
    parser_b.add_argument('-z', type=float,
                          help='an float for the accumulator')
    parser_b.add_argument('--min', dest='mn', action='store_const',
                          const=min, default=0,
                          help='smallest integer (default: 0)')
    return parser


if __name__ == '__main__':
    main()
Grier answered 16/11, 2019 at 14:51 Comment(0)
I
2

I had more or less the same requirements: Being able to set global arguments and being able to chain commands and execute them in order of command line.

I ended up with the following code. I did use some parts of the code from this and other threads.

# argtest.py
import sys
import argparse

def init_args():

    def parse_args_into_namespaces(parser, commands):
        '''
        Split all command arguments (without prefix, like --) in
        own namespaces. Each command accepts extra options for
        configuration.
        Example: `add 2 mul 5 --repeat 3` could be used to a sequencial
                 addition of 2, then multiply with 5 repeated 3 times.
        '''
        class OrderNamespace(argparse.Namespace):
            '''
            Add `command_order` attribute - a list of command
            in order on the command line. This allows sequencial
            processing of arguments.
            '''
            globals = None
            def __init__(self, **kwargs):
                self.command_order = []
                super(OrderNamespace, self).__init__(**kwargs)

            def __setattr__(self, attr, value):
                attr = attr.replace('-', '_')
                if value and attr not in self.command_order:
                    self.command_order.append(attr)
                super(OrderNamespace, self).__setattr__(attr, value)

        # Divide argv by commands
        split_argv = [[]]
        for c in sys.argv[1:]:
            if c in commands.choices:
                split_argv.append([c])
            else:
                split_argv[-1].append(c)

        # Globals arguments without commands
        args = OrderNamespace()
        cmd, args_raw = 'globals', split_argv.pop(0)
        args_parsed = parser.parse_args(args_raw, namespace=OrderNamespace())
        setattr(args, cmd, args_parsed)

        # Split all commands to separate namespace
        pos = 0
        while len(split_argv):
            pos += 1
            cmd, *args_raw = split_argv.pop(0)
            assert cmd[0].isalpha(), 'Command must start with a letter.'
            args_parsed = commands.choices[cmd].parse_args(args_raw, namespace=OrderNamespace())
            setattr(args, f'{cmd}~{pos}', args_parsed)

        return args


    #
    # Supported commands and options
    #
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('--print', action='store_true')

    commands = parser.add_subparsers(title='Operation chain')

    cmd1_parser = commands.add_parser('add', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    cmd1_parser.add_argument('add', help='Add this number.', type=float)
    cmd1_parser.add_argument('-r', '--repeat', help='Repeat this operation N times.',
                                               default=1, type=int)

    cmd2_parser = commands.add_parser('mult', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    cmd2_parser.add_argument('mult', help='Multiply with this number.', type=float)
    cmd2_parser.add_argument('-r', '--repeat', help='Repeat this operation N times.',
                                               default=1, type=int)

    args = parse_args_into_namespaces(parser, commands)
    return args


#
# DEMO
#

args = init_args()

# print('Parsed arguments:')
# for cmd in args.command_order:
#     namespace = getattr(args, cmd)
#     for option_name in namespace.command_order:
#         option_value = getattr(namespace, option_name)
#         print((cmd, option_name, option_value))

print('Execution:')
result = 0
for cmd in args.command_order:
    namespace = getattr(args, cmd)
    cmd_name, cmd_position = cmd.split('~') if cmd.find('~') > -1 else (cmd, 0)
    if cmd_name == 'globals':
        pass
    elif cmd_name == 'add':
        for r in range(namespace.repeat):
            if args.globals.print:
                print(f'+ {namespace.add}')
            result = result + namespace.add
    elif cmd_name == 'mult':
        for r in range(namespace.repeat):
            if args.globals.print:
                print(f'* {namespace.mult}')
            result = result * namespace.mult
    else:
        raise NotImplementedError(f'Namespace `{cmd}` is not implemented.')
print(10*'-')
print(result)

Below an example:

$ python argstest.py --print add 1 -r 2 mult 5 add 3 mult -r 5 5

Execution:
+ 1.0
+ 1.0
* 5.0
+ 3.0
* 5.0
* 5.0
* 5.0
* 5.0
* 5.0
----------
40625.0
Ian answered 15/9, 2020 at 12:55 Comment(0)
E
1

Another package which supports parallel parsers is "declarative_parser".

import argparse
from declarative_parser import Parser, Argument

supported_formats = ['png', 'jpeg', 'gif']

class InputParser(Parser):
    path = Argument(type=argparse.FileType('rb'), optional=False)
    format = Argument(default='png', choices=supported_formats)

class OutputParser(Parser):
    format = Argument(default='jpeg', choices=supported_formats)

class ImageConverter(Parser):
    description = 'This app converts images'

    verbose = Argument(action='store_true')
    input = InputParser()
    output = OutputParser()

parser = ImageConverter()

commands = '--verbose input image.jpeg --format jpeg output --format gif'.split()

namespace = parser.parse_args(commands)

and namespace becomes:

Namespace(
    input=Namespace(format='jpeg', path=<_io.BufferedReader name='image.jpeg'>),
    output=Namespace(format='gif'),
    verbose=True
)

Disclaimer: I am the author. Requires Python 3.6. To install use:

pip3 install declarative_parser

Here is the documentation and here is the repo on GitHub.

Engrossing answered 28/11, 2017 at 12:21 Comment(0)
G
0

In order to parse the sub commands, I used the following (referred from argparse.py code). It parses the sub parser arguments and retains the help for both. Nothing additional passed there.

args, _ = parser.parse_known_args()
Gaston answered 10/1, 2022 at 11:42 Comment(0)
A
0

You could use this multicommand-arg-parser I wrote. It's easy to use, write your template in json format and apply the parser.

https://github.com/antoniofrs/multicommand-args-parser

For example:

[
    {
        "id": "c1_id",
        "command": "command1",
        "help": "Select c1",
        "args": [ ],
        "subCommands": [
            {
                "id": "sc1_id",
                "command": "subcommand1",
                "help": "select sc1",
                "args": [ ]
            },
            {
                "id": "sc2_id",
                "command": "subcommand2",
                "help": "Select c12",
                "args": [ ]
            }
        ]
    },
    {
        "id": "c1_id",
        "command": "command2",
        "help": "Selected c2",
        "args": [ ]
    }
]

Creates the following structure:

command1 --my-args-here
command1 subcommand1 --my-args-here
command1 subcommand2 --my-args-here
command2 --my-args-here

Where id, which you can retrieve from agrs.command_id, will help you to get the command given in input by the user.

You can specify the list of args in the appropriate field (Check the repository readme).

Remember that subcommands inherit command arguments. (e.g subcommand1 contains also the arguments of command)

The json structure is recursive so you can create as many levels of subcommands as you like

Adorn answered 21/10, 2023 at 14:44 Comment(2)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Killy
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewCletacleti
P
0

The currently most-voted answer's code isn't reproducible.

In the other existing answers, the command line doesn't fail if the user doesn't specify a sub-command. In my use-case, I need the program to fail if no sub-commands are passed (and ideally display the help).

So here is how to do that :

import argparse
import sys


parser = argparse.ArgumentParser(description="My command line.")
subparsers = parser.add_subparsers(title="commands", dest="cmd")

evaluate_parser = subparsers.add_parser("evaluate", help="Run the evaluation using emulated keyboard.")
evaluate_parser.set_defaults(cmd="evaluate")

layout_parser = subparsers.add_parser("show_layout", help="Display the layout over the keyboard for debugging purpose.")
layout_parser.set_defaults(cmd="show_layout")

args = parser.parse_args()

if args.cmd is None:
    parser.print_help(sys.stderr)
    sys.exit(1)

Posterior answered 19/3 at 3:7 Comment(0)
S
-4

you can use the package optparse

import optparse
parser = optparse.OptionParser()
parser.add_option("-f", dest="filename", help="corpus filename")
parser.add_option("--alpha", dest="alpha", type="float", help="parameter alpha", default=0.5)
(options, args) = parser.parse_args()
fname = options.filename
alpha = options.alpha
Somatist answered 4/5, 2012 at 13:23 Comment(2)
This doesn't really answer the question. Also, optparse is deprecated (from the python docs "The optparse module is deprecated and will not be developed further; development will continue with the argparse module").Valance
Sorry for downvote, but this is not addressing the question i asked.Selemas

© 2022 - 2024 — McMap. All rights reserved.