Title: argparse: add_subparsers 'action' broken
Type: behavior Stage: resolved
Components: Documentation, Library (Lib) Versions: Python 3.3, Python 2.7
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: docs@python Nosy List: berker.peksag, derks, docs@python, paul.j3, spaceone
Priority: normal Keywords:

Created on 2015-02-20 07:24 by derks, last changed 2017-03-28 17:09 by paul.j3. This issue is now closed.

Messages (6)
msg236254 - (view) Author: BJ Dierkes (derks) Date: 2015-02-20 07:24

I came across issue9253 in trying to implement a default action for a subparser namespace.  In the absence of a 'default' option, I thought that it may be possible by adding an 'action' to 'add_subparsers'.  Per the documentation this should be possible:

action - the basic type of action to be taken when this argument is 
encountered at the command line

That said, custom actions on 'add_subparsers' doesn't appear to work at all:

    import argparse

    class CustomAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            print('Inside CustomAction')
            setattr(namespace, self.dest, values)

    root_parser = argparse.ArgumentParser(prog='myapp')
    sub_parser = root_parser.add_subparsers(dest='commands', action=CustomAction)
    args = root_parser.parse_args()


$ python --help
Traceback (most recent call last):
  File "", line 46, in <module>
    sub_parser = root_parser.add_subparsers(dest='commands', action=CustomAction)
  File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/", line 1661, in add_subparsers
    action = parsers_class(option_strings=[], **kwargs)
TypeError: __init__() got an unexpected keyword argument 'prog'

Erroneous documentation maybe?  Tested the same on Python 2.7 and 3.3.
msg238931 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-03-22 20:01
It certainly looks like a documentation issue.  `action` doesn't make sense here.  `add_subparsers` normally creates an Action of type `_SubParsersAction`.  

'action' for normal 'add_argument' command defines the Action type created at that time.  Conceivably a user might want to use a customized '_SubParsersAction', but it shouldn't be as easy as just specifying 'action' in the 'add_subparsers' command.

And specifying something other than the default 'store' action class for the arguments of the parsers doesn't make sense.

But I'm puzzled by the error message you got.  I'll have to run some tests to figure out what is going on.
msg239248 - (view) Author: SpaceOne (spaceone) * Date: 2015-03-25 10:49
In replay to msg238931 from paul j3 (paul.j3) *
> And specifying something other than the default 'store' action class for the arguments of the parsers doesn't make sense.
Of course it makes sense. If you e.g. want the action to be 'append' so that the subparser-name is appended to a already existing list / or to create a new list with the subparser-name as first argument.

parser.add_subparser(dest='foo', action='append')
parser.parse_args('bar').__dict__ == {'foo': ['bar']}
msg239268 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-03-25 16:23
OK, so you are thinking about what happens to the subparsers `dest` when the user names a command.  That isn't handled by the `store` action, but by the call of the subparsers action

    class _SubParsersAction(Action):    
        def __call__
            # set the parser name if requested
                if self.dest is not SUPPRESS:
                    setattr(namespace, self.dest, parser_name)
            namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)

Storing the parser_name is a minor part of this action.  The important, distinctive part is passing the parsing action to that subparser.

That storing could be done in an append way, but why?  There can only one `subparsers`, and it can be invoked only once.

None of the existing Action classes can replace _SubParsersAction, which is entered in the `registry` as `parsers`:

    In [11]: parser._registry_get('action','parsers')
    Out[11]: argparse._SubParsersAction
If you want to write your own version of this action, you can use it, either by replacing the existing class, or by changing this registry entry.
msg239275 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2015-03-25 16:53
As to the nature of the error when 'add_subparsers' is given an 'action' parameter:

'add_subparsers' does several things to 'kwargs' before it passes them to the relevant Action class.

     def add_subparsers(self, **kwargs):
        # adds 'parser_class'
        # removes 'title', 'description' (used in an argument group)
        # add 'prog'
        parsers_class = self._pop_action_class(kwargs, 'parsers')
        action = parsers_class(option_strings=[], **kwargs)

What I wrote earlier about using the registry is partly wrong.  The Action class is determined by either the 'action' parameter or the registry entry.

     In [17]: p._pop_action_class({}, 'parsers')
     Out[17]: argparse._SubParsersAction

     In [18]: p._pop_action_class({'action':'test'}, 'parsers')
     Out[18]: 'test'

So the 'action' parameter works - if you specify a compatible Action class.


Such a class must have the same __init__ signature, otherwise you'll get errors such the OP's.

It might be worth rewriting the documentation line so this is clearer.  Otherwise I recommend closing this issue.

     action = parsers_class(option_strings=[], **kwargs)
msg290746 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2017-03-28 17:09
I'm going to close this.  At most it calls for a documentation change.  But saying something like

   It must be compatible with argparse._SubParsersAction.

will just confuse the average user.  Any customization of this action class is an advanced topic, that requires familiarity with the code, not just the documentation.
Date User Action Args
2017-03-28 17:09:08paul.j3setstatus: open -> closed
resolution: rejected
messages: + msg290746

stage: resolved
2015-03-25 16:53:44paul.j3setmessages: + msg239275
2015-03-25 16:30:12paul.j3setassignee: docs@python

components: + Documentation
nosy: + docs@python
2015-03-25 16:23:56paul.j3setmessages: + msg239268
2015-03-25 10:49:24spaceonesetmessages: + msg239248
2015-03-25 10:45:04berker.peksagsetnosy: + berker.peksag, spaceone
2015-03-25 10:44:38berker.peksaglinkissue23777 superseder
2015-03-22 20:01:52paul.j3setnosy: + paul.j3
messages: + msg238931
2015-02-20 07:24:03derkscreate