classification
Title: argparser's subparsers.add_parser() should accept an ArgumentParser
Type: enhancement Stage: needs patch
Components: Library (Lib) Versions: Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Mickaël.Falck, bethard, chris.jerdonek, paul.j3
Priority: normal Keywords: patch

Created on 2013-02-14 07:01 by chris.jerdonek, last changed 2014-10-10 04:35 by paul.j3.

Files
File name Uploaded Description Edit
issue17204.py paul.j3, 2013-04-18 07:06
patch0.diff paul.j3, 2014-10-10 04:35 review
Messages (4)
msg182081 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2013-02-14 07:01
Currently, argparser's subparsers.add_parser() method (for adding sub-commands) takes the following input:

"This object has a single method, add_parser(), which takes a command name and any ArgumentParser constructor arguments, and returns an ArgumentParser object that can be modified as usual."

(from http://docs.python.org/dev/library/argparse.html#argparse.ArgumentParser.add_subparsers )

It would be nice if one could also pass an ArgumentParser object to add_parser().  This would allow the composition of parsers.  For example, if a library exposed an ArgumentParser command-line API, one could expose that library's commands as a sub-command of the parent project's command-line API using add_parser().
msg187226 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2013-04-18 07:06
The 'subparsers' object has a _parser_class attribute that is normally set to the class of the parent parser.  In the attached file I create a

    class CustomParser(argparse.ArgumentParser)

that makes a parser instance which copies all of the attributes of prototype parser.

    proto1 = argparse.ArgumentParser(prog='SUBPROG1')
    proto1.add_argument('--foo')
    subparsers._parser_class = CustomParser
    sub1 = subparsers.add_parser('cmd1', proto=proto1, help='parser based on proto1')

'sub1' is a functional copy of 'proto1'.  I think this does what you want without changing the argparse code.  There probably is a way of defining CustomParser (maybe its '__new__' method) so 'sub1' is actually 'proto1'.  But the copy approach appears to work just fine.
msg204372 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2013-11-25 17:41
http://stackoverflow.com/a/20167038/901925
is an example of using `_parser_class` to produce different behavior in the subparsers.

    parser = ArgumentParser()
    parser.add_argument('foo')
    sp = parser.add_subparsers(dest='cmd')
    sp._parser_class = SubParser # use different parser class for subparsers
    spp1 = sp.add_parser('cmd1')
    spp1.add_argument('-x')
    spp1.add_argument('bar')
    spp1.add_argument('vars',nargs='*')

In this case the SubParser class implements a `parse_intermixed_known_args` method that handles that `nargs='*'` argument.

http://bugs.python.org/issue14191

It shouldn't be hard to add `parser_class` as a documented optional argument to `add_subparsers`.
msg228950 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-10-10 04:35
In the attached patch I modified 'add_parser' to take a 'parser' keyword parameter.  If given, it is used as the parser, rather than create a new one.  Thus an existing parser, or one created with a custom ArgumentParser class, could be used as a subparser.  

In this sample script, a parent parser is used as subparser in 2 different ways - via the parent mechanism, and with this new add_parser parameter.

    parent = argparse.ArgumentParser()
    parent.add_argument('-f')
    parent.add_argument('bar')

    parser = argparse.ArgumentParser()
    sp = parser.add_subparsers(dest='cmd')
    p1 = sp.add_parser('cmd1', add_help=False, parents=[parent])
    p2 = sp.add_parser('cmd2', parser=parent)
    parent.add_argument('test')
    assert p2 is parent
    assert p1 is not parent
    print(parser.parse_args())

This change passes existing unittests.  I don't think there are any backward compatibility issues.
History
Date User Action Args
2014-10-10 04:35:04paul.j3setfiles: + patch0.diff
keywords: + patch
messages: + msg228950
2013-11-25 17:41:02paul.j3setmessages: + msg204372
2013-11-25 11:52:55Mickaël.Falcksetnosy: + Mickaël.Falck
2013-04-18 07:06:53paul.j3setfiles: + issue17204.py
nosy: + paul.j3
messages: + msg187226

2013-04-16 07:55:47Greg.Trahairsetnosy: - Greg.Trahair
2013-04-16 07:53:55Greg.Trahairsetnosy: + Greg.Trahair
2013-02-14 07:01:07chris.jerdonekcreate