""" Context: I am trying to set up a cookiecutter so that newly-created packages will come with a Jupyter notebook users can play with. That is, python -m package_name jupyter would open up a Jupyter quickstart notebook demonstrating the package's features. argparse.REMAINDER as the first argument isn't important for a top-level parser, since we can work around it by not using argparse at all, but using argparse.REMAINDER in a subparser seems like a pretty straightforward use case. Any time we want to dispatch a subcommand to a separate tool --- forwarding all following arguments --- we're going to need it. """ import argparse parser = argparse.ArgumentParser() parser.add_argument('command', default='cmdname') parser.add_argument('cmdname_args', nargs=argparse.REMAINDER) args = parser.parse_args('cmdname --arg1 XX ZZ --foobar'.split()) if args != argparse.Namespace(cmdname_args=['--arg1', 'XX', 'ZZ', '--foobar'], command='cmdname'): raise Exception(args) print('This is how argparse.REMAINDER works when there is an argument in front.') parser = argparse.ArgumentParser() parser.add_argument('--foo') parser.add_argument('command', default='cmdname') parser.add_argument('cmdname_args', nargs=argparse.REMAINDER) args = parser.parse_args('--foo B cmdname --arg1 XX ZZ --foobar'.split()) if args != argparse.Namespace(cmdname_args=['--arg1', 'XX', 'ZZ', '--foobar'], command='cmdname', foo='B'): raise Exception(args) print('This is how argparse.REMAINDER works there is an option in front.') parser = argparse.ArgumentParser() parser.add_argument('--foo') subparsers = parser.add_subparsers(dest='command') commandParser = subparsers.add_parser('cmdname') commandParser.add_argument('--filler-boundary-marker', dest='cmdname_args', nargs=argparse.REMAINDER) args = parser.parse_args('--foo B cmdname --filler-boundary-marker --arg1 XX ZZ --foobar'.split()) if args != argparse.Namespace(cmdname_args=['--arg1', 'XX', 'ZZ', '--foobar'], command='cmdname', foo='B'): raise Exception(args) print('This is how argparse.REMAINDER works with a visible "filler" name for the list of arguments.') parser = argparse.ArgumentParser() parser.add_argument('--foo') subparsers = parser.add_subparsers(dest='command') commandParser = subparsers.add_parser('cmdname') commandParser.add_argument('--filler-boundary-marker', dest='cmdname_args', nargs=argparse.REMAINDER) args = parser.parse_args('cmdname --filler-boundary-marker --arg1 XX ZZ --foobar --foo B'.split()) if args != argparse.Namespace(cmdname_args=['--arg1', 'XX', 'ZZ', '--foobar', '--foo', 'B'], command='cmdname', foo=None): raise Exception(args) print("If an optional argument is provided after cmdname instead of before, it will get interpreted as part of the argparse.REMAINDER instead of normally. And that's great! We don't even need to be paranoid about other functions of our command-line tool sharing arguments with the tool we want to wrap. Everything will be forwarded.") parser = argparse.ArgumentParser() parser.add_argument('--foo') subparsers = parser.add_subparsers(dest='command') commandParser = subparsers.add_parser('cmdname') commandParser.add_argument('positional_arg') commandParser.add_argument('cmdname_args', nargs=argparse.REMAINDER) args = parser.parse_args('cmdname can_put_anything_here --arg1 XX ZZ --foobar --foo B'.split()) if args != argparse.Namespace(cmdname_args=['--arg1', 'XX', 'ZZ', '--foobar', '--foo', 'B'], command='cmdname', positional_arg='can_put_anything_here', foo=None): raise Exception(args) print("If an optional argument is provided after cmdname instead of before, it will get interpreted as part of the argparse.REMAINDER instead of normally. And that's great! We don't even need to be paranoid about other functions of our command-line tool sharing arguments with the tool we want to wrap. Everything will be forwarded.") """ Note that this means we can fix the bug simply by, whenever the cmdname subparser is invoked and the cmdname subparser uses argparse.REMAINDER, automatically adding an imaginary first positional argument to the subparser and inserting that imaginary first positional argument into the stream before parsing the arguments to cmdname. https://github.com/python/cpython/blob/master/Lib/argparse.py#L1201 (Obviously it would be better to fix the underlying cause.) """ print('What we want to do is have a subparser that, in the case of one particular selection, forwards all following arguments to another tool.') print('script.py --foo B cmdname --arg1 XX ZZ --foobar should dispatch to cmdname --arg1 XX ZZ --foobar.') parser = argparse.ArgumentParser() parser.add_argument('--foo') subparsers = parser.add_subparsers(dest='command') commandParser = subparsers.add_parser('cmdname') commandParser.add_argument('cmdname_args', nargs=argparse.REMAINDER) parser.parse_args('--foo B cmdname --arg1 XX ZZ --foobar'.split()) # error: unrecognized arguments: --arg1