Reputation: 4467
I use python 3, i want to set argparse to always set the default key in relation to a predefined optional argument of my choice.
E.g. I pass some input files to the script. An optional argument specify if the input file should be rotated by the script. I get a list of input files but I also want a list of the same size containing 'True' where the user want to rotate the input file, or the default false when the user has not made a choice
For example
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--input', action='append', required=True, help='Input pdf files.')
parser.add_argument('--rotate', action='store_true', default=False, help='Rotate the input file')
args = parser.parse_args()
print(args)
I call the script with this command:
python argparse_test.py --input 1.pdf --input 2.pdf --rotate
This is the output
Namespace(input=['1.pdf', '2.pdf'], rotate=True)
But I want this output instead
Namespace(input=['1.pdf', '2.pdf'], rotate=[False,True])
Please note that --rotate
can be ANY operation that must be performed by the python script i'm writing on the --input
file before the --rotate
itself. So i need to know which --input
file --rotate
is refering to
Upvotes: 0
Views: 1386
Reputation: 231738
So
python argparse_test.py --input 1.pdf --input 2.pdf --rotate
is supposed to mean - set rotate
to True for 2.pdf
because it follows that name, but leave it False for 1.pdf
because there isn't a --rotate
flag following it?
It can't work that way. flagged arguments like --rotate
and --input
can occur in any order. And store_true
arguments can't operate in the append
mode like your --input
.
Some alternatives that might work:
--input as nargs=2
python argparse_test.py --input 1.pdf 0 --input 2.pdf 1
Namespace(input=[['1.pdf','0'], ['2.pdf','1']])
--input as nargs='+'
python argparse_test.py --input 1.pdf --input 2.pdf 1
Namespace(input=[['1.pdf'], ['2.pdf','1']])
In other words, the user gives an added string after the file name to indicate whether it is to be rotated or not.
I was going to suggest an append_const
for rotate
, but there's no way to insert a default into the list.
I can imagine defining a pair of custom action classes - see below.
What I think Waylan was suggesting was to have 2 append
lists, one for files that should be rotated, and another for ones that shouldn't.
How would you convey requirements like this in the usage and help message? What may seem logical to you in the middle of development, might not be so obvious to another user, or to yourself six months from now.
Custom Action classes that seem to behave as desired. This isn't a trivial customization, but not a complicated or obscure one either. It does require some detailed understanding of the argparse
code.
import argparse
class Input_action(argparse._AppendAction):
def __call__(self, parser, namespace, values, option_string=None):
# assume attribute has already been set to []
items = getattr(namespace, self.dest)
items.append(values)
setattr(namespace, self.dest, items)
dest = 'file_rotate'
values = False
items = getattr(namespace, dest)
items.append(values)
setattr(namespace, dest, items)
class Rotate_action(argparse._StoreTrueAction):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const)
dest = 'file_rotate' # not same as self.dest
items = getattr(namespace, dest)
if items:
items[-1] = True
setattr(namespace, dest, items)
parser = argparse.ArgumentParser()
parser.add_argument('-i','--input', action=Input_action)
parser.add_argument('-r','--rotate', action=Rotate_action)
parser.print_help()
# simpler to initialize these attributes here than in the Actions
ns = argparse.Namespace(input=[], file_rotate=[])
print parser.parse_args(namespace=ns)
output:
1030:~/mypy$ python stack28967342.py -i one -r -i two -i three -r
usage: stack28967342.py [-h] [-i INPUT] [-r]
optional arguments:
-h, --help show this help message and exit
-i INPUT, --input INPUT
-r, --rotate
Namespace(file_rotate=[True, False, True], input=['one', 'two', 'three'], rotate=True)
Note the 2 lists in the resulting Namespace. The rotate
attribute isn't needed, but removing it requires more work. The help
does not indicate anything about the special pairing of options.
Here's an alternate Action pair that works with one list and composite object with name and rotate attribute. It might be easier to generalize this approach.
class MyObj(argparse._AttributeHolder):
"simple class to hold name and various attributes"
def __init__(self, filename):
self.name = filename
self.rotate = False
# define other defaults here
class Input_action(argparse._AppendAction):
def __call__(self, parser, namespace, values, option_string=None):
items = argparse._ensure_value(namespace, self.dest, [])
items.append(MyObj(values))
setattr(namespace, self.dest, items)
class Rotate_action(argparse._StoreTrueAction):
def __call__(self, parser, namespace, values, option_string=None):
# with default=SUPPRESS, rotate does not appear in namespace
dest = 'input' # could make this a parameter
items = getattr(namespace, dest)
if items:
items[-1].rotate = True
# no need to set, since I'm just modifying an existing object
parser = argparse.ArgumentParser()
parser.add_argument('-i','--input', action=Input_action,
help='create a default input object with this name')
parser.add_argument('-r','--rotate', action=Rotate_action, default=argparse.SUPPRESS,
help = 'set rotate attribute of preceeding input object')
parser.print_help()
print parser.parse_args()
producing args like
Namespace(input=[MyObj(name='one', rotate=True), MyObj(name='two', rotate=False), MyObj(name='three', rotate=True)])
Upvotes: 1