Christopher Shroba
Christopher Shroba

Reputation: 7574

Can I direct Python's argparse to treat all arguments after a positional arg as also positional, even if they match an optional arg?

I am writing a utility (call it runner) which, as its main functionality, runs another script. That script to be run is passed as an argument, as well as any arguments that should be passed to the script. So, for example:

runner script.py arg1 arg2

will run script.py as a subprocess, with arg1 and arg2 as arguments. This is easy enough, using the following:

parser = argparse.ArgumentParser()

parser.add_argument("script", help="The script to run")
parser.add_argument("script_args", nargs="*", help="Script arguments", default=[])

args = parser.parse_args()

The complication is that I have some additional arguments passed to runner, which modify how the script will be called. One of these is --times, which will be used to determine how many times to run the script. So:

runner --times 3 script.py arg1 arg2

will run script.py three times, with arg1 and arg2 as arguments.

The problem I am trying to solve is this. I want any arguments after the script name to be treated as arguments to pass to the script, even if they match argument names of runner. So:

runner script.py arg1 arg2 --times 3

should run script.py once, and pass the arguments arg1, arg2, --times, and 3 to script.py.

I can't find any way to tell the ArgumentParser to treat all arguments after the script arg as positional.

I know the caller of the script could force this behavior using the -- special argument, so the following would yield the desired behavior:

runner -- script.py arg1 arg2 --times 3

but I want the program to take care of this instead. Is this behavior possible?

Upvotes: 2

Views: 457

Answers (1)

D-E-N
D-E-N

Reputation: 1272

First of all, you should not do this. It is more clear, if you make it explicit which arguments apply to which program (call), maybe you can sourround the whole script parameters of the called script in quotations like:

runner script.py "arg1 arg2 --times 3"

But if you want to do this your way, you can overwrite the ArgumentParser.parse_arguments function with your own. I only build an example to illustrate how it is possible (for me it's working), but i do not know if there are any sideeffects:

import argparse
from typing import Optional, Sequence


class ErrorCatchingArgumentParser(argparse.ArgumentParser):

    def parse_args(self, args: Optional[Sequence[str]] = ...) -> argparse.Namespace:
        ns, a = super().parse_known_args(args=args)
        args.insert(args.index(ns.script), "--")

        return super().parse_args(args=args)


if __name__ == '__main__':
    arguments = ["--time", "3", "myscript", "my_args", "myargs2", "--time", "5"]

    parser = ErrorCatchingArgumentParser()

    parser.add_argument("script", help="The script to run")
    parser.add_argument("--time", help="The script to run")
    parser.add_argument("script_args", nargs="*", help="Script arguments", default=[])

    print(arguments)
    namespace = parser.parse_args(arguments)

    print(arguments)
    print(namespace)

Output:

['--time', '3', 'myscript', 'my_args', 'myargs2', '--time', '5']
['--time', '3', '--', 'myscript', 'my_args', 'myargs2', '--time', '5']
Namespace(script='myscript', time='3', script_args=['my_args', 'myargs2', '--time', '5'])

It

  1. parses the given args with parse_known_args of superclass to get all arguments set (and do not raise an error)
  2. search for your script variable and insert the -- before it in the argument list
  3. parses the arguments with the parse_args function of superclass to get "the real" arguments

In the output you can see the original arguments, the manipulated arguments and the created namespace.

But i think you should NOT do this, anyway.

Upvotes: 1

Related Questions