Repository navigation
argparse: optional subparsers #53499
Description
Activity
**NOTE**: This is a re-post of http://code.google.com/p/argparse/issues/detail?id=47
What steps will reproduce the problem?
parser = argparse.ArgumentParser() sub = parser.add_subparsers() sub.add_parser("info") parser.add_argument("paths", "+") parser.parse_args(["foo", "bar"])
What is the expected output? What do you see instead?
Expected behavior is that, failing to match one of the subparser inputs
("info"), the parser checks if the argument matches any of the top-level
arguments, in this case the 'paths' multi-arg. In other words, it should be
possible to make the subparser be optional, such that when the subparser
argument fails to retrieve any valid subparser, the remaining args are
parsed as if no subparser exists. At present, it does not seem possible to
make a subparser be optional at all.
Perhaps this could be exposed to the user as:parser.add_subparsers(nargs=argparse.OPTIONAL)
or something to that effect. Or, allow a default subparser to be specified.
I.e.,sub = parser.add_subparsers() info = sub.add_parser("info") main = sub.add_parser("main") sub.default = main
I'm sure the point will come up that the current behavior is correct,
because given a subparser like "info", a user could easily make a mistake
like "myapp ino foo bar" and rather than get a safe error be given
something unexpected. For this reason, I think the default behavior is
usually going to be correct. BUT, it would still be nice if it could be
optional, so that developers could be free to make that call. Sometimes the
potential user errors aren't really an issue, and having to explicitly set
a subparse arg every time can be a nuissance.- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
on Jul 13, 2010 Changed the title, so it shows that the feature request is for argparse.
I've added Steven as nosy so he knows this was reposted here. I've also set the priority to low. Personally I'm at least -0 on this, since if I use a command that has subcommands I expect to get an error if I supply an invalid subcommand. As you say, however, the command designer *could* be supplied with the opportunity to shoot themselves in the foot :)
The issue isn't going to go anywhere unless someone proposes a patch, though.
Actually, this is a rather common concept. Broadly used tools like for example Git use this kind of subcommand handling.
This command shows all remotes:
git remote (i.e. is like git remote list)Showing/removing remotes is done using subsubcommands:
git remote show [...]
git remote rm [...]That, in combination with the explicit design goal that "[argparse] isn't dogmatic about what your command line interface should look like" should be enough reason to be wanting this, in my humble opinion.
See also 9540, which has an alternate proposal (that I don't like as much) for how to handle parser arguments supplied after subparsers are declared.
Reviewing this, I'm now +1 on fixing this *somehow*, since clearly there is an ambiguity here that needs to be resolved.
Seems like there's minimally the bug that argparse should currently throw an error if you add an argument after subparsers (since that argument will never be parsed under the current semantics).
I do believe that supporting an optional command like the "git remote" example is useful, but as RDM suggests, this probably won't go anywhere unless someone proposes a patch.
To expand on my case from bpo-9540, I have a bunch of commands, each of which should enable a specific subset of options only available the individual command, but all of the commands share the same behavior in taking nargs='*' positional arguments:
./script.py --global-option command --command-option arg1 arg2 arg3
For example:
./backups.py -c /etc/tarsnap.conf make --no-expire job1 job2
If no positional arguments are given, all jobs defined in the config file are run. Or, in the above example, only "job1" and "job2" are run.
The positional arguments are the same for *all* commands. Now I can define them separately for each subparser, which is what I'm currently doing, but I kind of like having the global usage instructions (script.py -h) indicating the fact that positional arguments can be passed after the command.
In fact, right now I'm able to sort of achieve this by defining the positional nargs arguments both globally (to have them show in usage) and in each subparser (to have them parsed). This wouldn't be possible anymore if argparse where to throw an error after adding arguments after a subparser, although probably a more correct behavior.
Anyway, while the two issues are clearly related, I don't think that the two are necessarily mutually exclusive. argparse could allow both optional subparsers (if no subparser matches), as well as pass control back to the parent parser once an already matched subparser is no longer able to handle further command line input. Or optionally, support defining subparsers as "options only", so that positional arguments would always be handled by the parent parser.
Now, I can see how this could potentially become messy if we start talking about these positional arguments handled by the parent then being followed by more flags, which would then presumably also be handled by the parent etc. On the other hand, my use case doesn't seem that strange to me.
Stable releases don’t go into stable branches, so I’m editing versions. I also remove 3.3 since it doesn’t exist now, it means “this won’t go in 3.2”.
Wow, it is late. I wanted to write: New features don’t go into stable branches.
Trying to spec this, here is a proposed API:
parser = argparse.ArgumentParser() sub = parser.add_subparsers(default='show') sub_show = sub.add_parser('show') sub_add = sub.add_parser('add')
If default isn't passed, the subcommand isn't optional.
If default is passed, and no explicit subcommand is given,
the default subcommand is picked.
Arguments are given to the top parser; passing arguments
to the subcommand requires naming it explicitly.As far as motivation, I'd like to change a program that
uses --choice options (that can have a default) to use
more expressive subcommands. Some programs rely on implicit
subcommands a lot; the ip command on linux is a good
example.I think the proposed API looks fine and should be backwards compatible since add_subparsers will currently throw an exception with a default= argument.
In case someone feels like writing a patch, you'll want to look at _SubParsersAction.__init__, which will need to grow the default= argument, and pass a different nargs= argument on. I think you'll need to define a new nargs type which means you probably also need to look at ArgumentParser._get_nargs_pattern as well.
I spent some time looking at this, as I was interested in
using this pattern to simulate what git and hg do. I
considered a few modifications and then found this bug. I
think the default keyword passed to
_SubParsersAction.__init__ makes sense.I started on a patch, that looks promising, but I'm having
trouble getting the regexp right.Here's a changeset higlighting where I think the
problematic regexp is:
https://bitbucket.org/bewest/argparse/changeset/938e1e91ddd0https://gist.github.com/1202975#file_test_opt_subcommand.py
Is the meager little test I put together.16 remaining items
is answered by this change in how
requiredarguments are tested, and how subparsers fell through the cracks.Another Stackoverflow question triggered by this issue
http://stackoverflow.com/questions/23349349/argparse-with-required-subparser
My answer to
http://stackoverflow.com/questions/23349349/argparse-with-required-subparser
is getting a slow but steady stream of + scores; so the
required subparserissue is still bothering people.This particular question addresses the problem that the error message has when a required subparser is missing - it can't format the error with the default dest - SUPPRESS. The may be a another bug issue that addresses that.
Anyways, due to this continued attention, I'm going to raise the priority for this issue.
I've attempted to address some of the backward/forward compatibility issue with subparsers becoming optional by default (vs required by default in python2) with this pull request: #3027 (would love to get a review as well!)
I am now reviewing the PR added to the other issue by Anthony. This ticket has a lot of discussion; it would be good to check which parts are addressed by the other ticket, and particularly if the problems noted by Mike and others are now fixed.
My patch mainly addresses the regression pointed out by mike bayer (zzzeek)'s comment.
The other PR is now merged in 3.7, and won’t be backported (it changes default behaviour and adds a new param).
In a recent stackoverflow question a user wanted this optional-subparsers ability in Python 2.7.
Short of modifying the _parse_known_args method, the best I could suggest was a two stage parsing. That is, one parser without the subparsers. This uses parse_known_args, and if a 'cmd' is provided passes the 'extras' to one that handles subparsers.
---
Another issue which I don't think has been addressed is the 'usage' when subparsers are optional. At least with 3.5, subparsers are displayed with the choices: {'cmd1', 'cmd2', ...}, but no indication of being optional. An optional positional (with ? nargs) would normally be displayed as
prog [-h] [{'one', 'two'}] ...My guess is that 'usage' adds the [] when positionals nargs='?', without regard to the 'required' attribute (I should verify this from code).
I'm undecided as to whether we want the brackets or not. It's more accurate, but makes the usage messier. And the 'help' grouping for 'optional-positionals' is the subject of other bug/issue(s).
I haven't checked it the patch has changed this behavior.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDoc issues
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: