Reputation: 7574
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
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
parse_known_args
of superclass to get all arguments set (and do not raise an error)script
variable and insert the --
before it in the argument listparse_args
function of superclass to get "the real" argumentsIn 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