Create parser with subcommands in argparse, custom

2019-08-11 02:19发布

I'm very new to this module so please bear with me. I have the following code:

reader.py

import argparse

parent_parser = argparse.ArgumentParser(description="Read text files.")
parent_parser.add_argument('filename', help='TXT file', type=file, nargs='+')
parent_parser.add_argument('--verbose', '-v', action='store_true', 
        help="Verbosity on")

child_parser = parent_parser.add_subparsers(title="subcommand",
        help="Subcommand help")
new_file_command = child_parser.add_parser('new', help="New text file")
edit_file_command = child_parser.add_parser('edit', help="Edit existing text file")

args = parent_parser.parse_args()

What I'm trying to achieve might not be the standard way of how parsers and unix command line utilities work. If that is true, please correct me as I'd like to have standardized app.

This is what I'm trying to achieve:

  • if you run bare script with positional argument(s) like this: python reader.py some.txt I'd like to be able to just parse it and pass it to function that reads the text file, of course I want to accept optional arg verbose as well
  • if you run subcommand 'new' (new_file_command), I do not want to have positional argument filename to be required, instead I want to pass a string and create new text file like this: python reader.py new another.txt
  • if you run subcommand 'edit' (edit_file_command) I want to pass existing file in path and check for it (like you use type=int in add_argument) and then maybe pass it to function that opens editor, something like this: python reader.py edit some.txt

Again, I'm not sure if this is how command line apps/scripts are supposed to behave. I read the docs and looked at examples but it's still isn't clear to me how sub parsers work. I tried looking at Click module but that seems to me even more complicated.

Any help appreciated. Thanks!

1条回答
女痞
2楼-- · 2019-08-11 03:05

So three sample calls are:

python reader.py some.txt 
python reader.py new another.txt
python reader.py edit some.txt

The easiest way to handle these is with one 'optional' positional, and one required one.

parser = ArgumentParser...
parser.add_argument('-v','--verbose', ...)
parser.add_argument('cmd', nargs='?', default='open', choices=['open','edit','new'])
parser.add_argument('filename')

For your 3 samples, it should produce something like:

namespace(cmd='open', filename='some.txt')
namespace(cmd='new', filename='another.txt')
namespace(cmd='edit', filename='some.txt')

cmd is an optional positional argument. If it is missing, the one string will be allocated to filename, and cmd gets its default. It's easier to do this than trying make a subparsers optional.


As for your current parser:

parent_parser = argparse.ArgumentParser(description="Read text files.")
parent_parser.add_argument('filename', help='TXT file', type=file, nargs='+')

I would not recommend using type=file. Better to use FileType or the default string (which lets you open the file in a with context later).

As to the nargs='+', do you really want to allocate 1 or more strings to filename? Or were you thinking of '?', which would be 0 or 1, i.e. making it optional?

parent_parser.add_argument('--verbose', '-v', action='store_true', 
        help="Verbosity on")

child_parser = parent_parser.add_subparsers(title="subcommand",
        help="Subcommand help")
new_file_command = child_parser.add_parser('new', help="New text file")
edit_file_command = child_parser.add_parser('edit', help="Edit existing text file")

Mixing this filename positional which accepts a variable number of values, with a subparsers argument (a positional that expects either new or edit) could be a problem.

I expect 'python reader.py some.txt' to object that the subparser command is missing. 'python reader.py new another.txt' will try to allocate new to filename, and another.txt to subparser, and raise an error.

It would be better to expect a subparsers command in all 3 cases:

parent_parser = argparse.ArgumentParser(description="Read text files.")
parent_parser.add_argument('--verbose', '-v', action='store_true', 
        help="Verbosity on")
child_parser = parent_parser.add_subparsers(title="subcommand",
        help="Subcommand help", dest='cmd')
open_file_command = child_parser.add_parser('open', help="Open text file")
open_file_command.add_argument('filename', help='TXT file')
new_file_command = child_parser.add_parser('new', help="New text file")
new_file_command.add_argument('filename', help='TXT file')
edit_file_command = child_parser.add_parser('edit', help="Edit existing text file")
edit_file_command.add_argument('filename', help='TXT file')

Each of commands, 'open','new','edit', expects a 'filename'.

Trying to avoid the use of an open command is going to create more difficulties than it's worth.

(There is a bug/feature in the latest argparse that makes subparsers optional, but you shouldn't take advantage of that without really knowing the issues.)


With:

parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose') 
parser.add_argument('cmd', nargs='?', default='open', 
    choices=['open', 'edit', 'new']) 
parser.add_argument('filename', nargs='+') 

I expect reader.py new customstring to give

namespace(cmd='new', filename=[customstring])

which could be used as:

if args.cmd=='new':
with open(args.filename[0] + '.txt', 'w') as f:
     # do something with the newly created file

open and edit would use different open modes.


Beware that in Py3, subparsers are not required. That is, if one of the subcommands is not provided, it won't raise an error. That's an inadvertent change from early versions.

Argparse with required subparser

查看更多
登录 后发表回答